Skip to content

feat(macos): menu bar companion + widget, with CodexBar-style usage monitor integrated into Usage - #5196

Merged
lidge-jun merged 63 commits into
devfrom
feat/macos-app
Sep 20, 2026
Merged

lidge-jun merged 63 commits into
devfrom
feat/macos-app

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 19, 2026

Copy link
Copy Markdown

Summary

Supersedes #5160 (thanks @JayYun98). The CodexBar-style monitor from that PR is folded onto the maintainer companion in app/, so there is one macOS companion: menu bar item + popover + a WidgetKit widget, all fed by the proxy's own usage accounting instead of a second loopback server.

Proxy (src/companion/, src/usage/timeline.ts, management routes)

  • GET/PUT /api/companion/settings — persisted at <opencodex home>/companion.json (atomic, 0600, unknown keys rejected, corrupt file → defaults + corrupt: true, never overwritten on GET). Partial PUT or { reset: true }.
  • GET /api/usage/timeline?hours=6|24|72|168&bucketMinutes=…&metric=total|input|output|cached&aggregation=sum|average|max&grouping=model|modelAccount&models=… — buckets built from usageAttributions() (same per-attempt accounting as the Usage page, no double counting), ≤2000 buckets, top 23 series + other fold (avg/max fold from request-level maps, not from already-aggregated rows), 15s cache keyed on a bucket-aligned now, in-flight promise shared without inheriting the first caller's abort signal, truncated metadata from the bounded ledger read.
  • ocx companion show|set <k>=<v>…|reset.

Dashboard — no second Usage tab. The existing Usage workspace gets a Menu bar & widget section: live timeline preview (line / stacked), window / metric / grouping / aggregation, menu-bar metric, popover section switches, advanced template + model/provider filters, autosave with saved/error/retry, reset to defaults. All ten locales.

App (app/Sources/MenuBarCore, MenuBarUI) — menu-bar title driven by menuBarMetric / menuBarTemplate ({requests} {totalTokens} {inputTokens} {outputTokens} {costUsd} {quotaPercent}), TODAY / chart / MODELS / ACCOUNTS / providers sections toggled by settings, hidden providers honoured, native AppKit timeline chart with a fit-to-width legend, privacy-safe WidgetSnapshot written atomically + WidgetCenter.reloadAllTimelines().

Widget (app/Sources/OpenCodexWidget, packaged as OpenCodex.app/Contents/PlugIns/OpenCodexWidget.appex) — small / medium / large, states: running, degraded, unreachable, unauthorized, loading, missing, corrupt, stale (>10 min), deep link to #/usage, 5-minute timeline.

  • Snapshot path is the widget's bundle container (~/Library/Containers/com.opencodex.menubar.widget/Data/Library/Application Support/OpenCodex/snapshot.json) rather than an App Group: with ad-hoc / non-team signing the App Group container is refused by TCC, the bundle container is not. Switching to an App Group is a two-line change once the release is team-signed.
  • The appex is linked with -e _NSExtensionMain (same as Apple's own widget appexes). Without it chronod spawns the process and it exits 0 before the WidgetBundle connects, so the widget never appears in the gallery — this is what made the earlier packaging "register but not render".

Follow-up round (high-volume UX)

  • Default headline is tokens, not requests (100k+ requests/day makes the count meaningless as a glanceable number). Tokens render as integer K/M/B/T with no decimals — 2401634303 → 2B, 333400000 → 333M — in the menu bar title, popover, widget and dashboard (Format.tokens(integer:), formatCompanionTokens). Popover TODAY and widget rows are ordered tokens · requests · cost.
  • Models on chart is a first-class block under the chart: provider-grouped scrollable list (max 280px), Switch per model and per provider (mixed state when partial), last-known total per row so rows don't reorder under the cursor, n of N on chart + Show all (→ models: null). Pure helpers groupCompanionModels / toggleCompanionModels are unit-tested.
  • Install card driven by app presence: the app sends User-Agent: OpenCodexMenuBar/<version>, the settings route records companion.lastSeenAt, and the dashboard shows either Menu bar app connected · just now (steps collapsed under "Install on another Mac") or an open 3-step install card (release ZIP → Applications, right-click → Open for Gatekeeper, xattr -d com.apple.quarantine). Settings are re-fetched every 60s (skipped while an edit is pending) so the state stays live.
  • macOS 26 Liquid Glass: popover surface is NSGlassEffectView under #available(macOS 26, *), NSVisualEffectView(.popover) otherwise; widget honours widgetRenderingMode == .accented (widgetAccentable() headline/chart, monochrome opacity steps instead of provider colours).

scripts/build-macos-app.sh builds both products, stages/versions/signs the appex before the host app; tests/gui/macos-build-script.test.ts asserts the packaging contract.

Screenshots

Popover on macOS 26 (Liquid Glass, tokens-first TODAY, 120k req / 2.4B tok seeded) Widgets small / medium / large with integer token abbreviation
popover glass widgets tokens
Usage → Menu bar & widget: install card (connected) + scrollable model switches Mixed provider switch after turning one model off, Show all appears
install + models mixed
Menu bar popover (settings-driven sections, stacked chart) Widgets: small / medium (chart fallback when no quota) / large
popover widgets
Widget gallery (all three families) Usage → Menu bar & widget (line chart)
gallery companion line
Companion controls Popover section switches + reset
controls switches
Stacked chart + Advanced (template, filters) Usage overview (unchanged tabs, new section tab)
stacked advanced overview

Not captured: a quota bar — the seeded demo proxy has no provider that reports limits (/api/provider-quotas returns coverage only), so the widget shows its chart fallback; widget stale/missing states need a 5-minute WidgetKit refresh cycle and were verified via the reader's unit path instead.

Merge-readiness round

  • Rebased onto the current dev tip (1b57a57218); all 59 commits replayed with no conflicts. The earlier red test 3/4 was a base defect — the base predated the follow-up test fix in dev — and the rebase resolves it.
  • Dropped devlog/_plan/260725_macos_menubar_app/ (9 files, 2,605 lines). dev published the same unit as closed under _fin/ in 8ae52e4291, and three of the duplicates still carried the 12.4M/36.5B text this PR corrects in _fin.
  • package-macos now gates on validate-dispatch, and its upload-artifact pin moves to the v7.0.1 SHA ci.yml already uses — one pin per action, and the pairing download-artifact v8 expects.

Verification

  • cd app && swift build -c release && swift build -c release --product OpenCodexWidget && swift run MenuBarCoreTests (118 pass) && swift run MenuBarUITests (10 pass)
  • bun test gui/tests/usage-companion-utils.test.ts (7 pass), tests/server/companion-settings.test.ts (4 pass)
  • bun test tests/gui/macos-build-script.test.ts (9 pass), tests/ci-workflows (144 pass), README parity (53 pass)
  • bun run typecheck, bun run structure:check, bun run lint:i18n, bun run build:gui, git diff --check
  • OUT=.tmp/app-out bash scripts/build-macos-app.shpluginkit -m -v -i com.opencodex.menubar.widget registers, widget renders in the gallery and Notification Center (screenshots above), appex entitlements = com.apple.security.app-sandbox only.
  • File-size ratchet: NEW_OVERSIZED src/codex/history-provider.ts / GREW tests/server/server-combo-failover-e2e.test.ts are reported identically on origin/dev; neither file is touched here.
  • Hosted CI on the exact head: rebased onto the current dev tip and green across the full matrix (typecheck, gates, structure, privacy, four test shards, macOS/Linux/Windows, docker smoke, npm-global smokes, macOS app build).

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed (README ×8, macOS guide, structure/, devlog _fin/260725_macos_menubar_app/051).
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults (snapshot carries no keys/labels beyond provider display names; settings file 0600; companion routes sit behind the existing management auth).

Co-authored-by: Jyun1998 yjunwoo14@gmail.com
Co-authored-by: Jay Yun 61632100+JayYun98@users.noreply.github.com

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • All CI tests are green on my local testing.

  • I pushed my PR to the latest dev commit.

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

Link to Devin session: https://app.devin.ai/sessions/f2dce68b7ca349e3ba39dab55bc12300
Open in Devin Desktop: https://app.devin.ai/desktop/session/f2dce68b7ca349e3ba39dab55bc12300?variant=devin
Requested by: @lidge-jun

@devin-ai-integration

Copy link
Copy Markdown
Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 8be53f3e-3205-49f7-ae7a-5f7432e637f9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the intake: hygiene-blocked Deterministic PR hygiene checks failed label Sep 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Deterministic hygiene checks failed.

  • unsponsored_surface — This changes an authentication, workflow, release-automation, or dependency surface. MAINTAINERS.md requires security review for these; ask a maintainer to apply maintainer-sponsored once they have reviewed it. Paths: .github/workflows/ci.yml, .github/workflows/release.yml, package.json.
  • missing_coauthor_credit — This pull request says it reimplements, supersedes, carries, or rebases another author's pull request, but no Co-authored-by trailer names that author. Prose in a commit body is not read by anything; the trailer is what GitHub counts. Add it to the description or a commit, or obtain attribution-approved. Paths: #5160.

@github-actions github-actions Bot added the enhancement New feature or request label Sep 19, 2026
@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • review readiness checklist open (0/4 boxes ticked).

What to do

  • Tick all four boxes in the PR description once you're done (currently 0/4).

Review readiness checklist

  • ⬜ All CI tests are green on my local testing.
  • ⬜ I pushed my PR to the latest dev commit.
  • ⬜ I resolved all correct Codex and CodeRabbit findings.
  • ⬜ My PR is ready for review.

0/4 boxes ticked.

Automatic draft conversion failed. Please convert this pull request to a draft manually until every box above is ticked.

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 64 / 80

이 PR은 맥 메뉴막대 앱을 app/에 새로 넣고, 그 앱이 나중에 쓸 설정이랑 사용량 시간표를 프록시 관리 API에 붙인 작업이에요. 베이스는 dev가 맞아요.

브랜치에 실제로 있는 건 메뉴막대 앱(상태 아이콘, 팝오버, 프록시 끄기, 제공자 스위치), 그걸 zip으로 묶는 스크립트와 릴리스 잡, 가이드 다섯 언어, 그리고 GET/PUT /api/companion/settings, GET /api/usage/timeline, ocx companion 읽기 명령이에요.

제목이 말하는 위젯이랑 Usage 화면에 설정을 넣는 일은 이 diff에 없어요. gui/ 변경이 없고, 위젯 확장(.appex)도 없어요. 설명은 "작업 중"이고, 초안이고, 준비 체크는 0/4예요. 아직 합칠 상태가 아니에요.

CI도 빨개요. test 1/4macos-app을 게이트 목록에 넣었는데 tests/ci-workflows/ci-structure-gate.test.ts가 예전 문장을 그대로 기대해요. test 2/4는 README 번역이 영어 README 해시랑 어긋나고, 메뉴막대 가이드는 영어가 소제목(h3)인데 다른 언어는 접기(details)라서 구조가 안 맞아요. 경로 필터 테스트도 app/**를 아직 몰라요. test 3/4의 파일 크기 검사는 이 PR이 안 고친 src/codex/history-provider.tstests/server/server-combo-failover-e2e.test.ts를 집어요. 이 PR 탓인지는 dev에서 같은 검사가 통과하는지로 갈려요. 위생 검사는 maintainer-sponsored가 없고, #5160 작성자 JayYun98의 Co-authored-by가 없다고 해요. #5160은 이미 닫혀 있어요.

src/server/management/usage-timeline-routes.ts - 15초 캐시가 첫 요청의 취소 신호를 다음 요청이랑 같이 써요. 먼저 연 쪽이 연결을 끊으면 나중 요청도 같이 실패해요. 캐시에서 꺼낸 응답은 jsonResponse에 요청이랑 설정을 안 넘겨요. 브라우저가 허용하는 주소가 항상 http://localhost:<포트>로 박혀요. 대시보드가 다른 주소면 두 번째 조회만 막혀요.

src/usage/timeline.ts - 캐시 키는 시각을 통 크기로 자르는데, 그래프 시작은 요청이 도착한 시각 그대로예요. 통 크기가 시간을 딱 나누지 않으면 같은 키인데 시작이 한 칸 다른 그래프가 나갈 수 있어요. 줄이 24개를 넘기면 나머지를 other로 더하는데, 합계가 아닌 평균이나 최댓값도 그냥 더해요. 테스트는 합계만 봐요.

src/cli/dispatch.ts companion - 명령은 설정을 읽기만 해요. src/cli/capabilities.tsmutates: true이고 요약은 설정한다고 적혀 있어요. 저장은 API의 PUT뿐이고, 메뉴막대 앱은 이 설정을 아직 읽지 않아요. 설정 파일이 깨져 있으면 기본값으로 조용히 바뀌고 저장 시각도 비어요. 다음에 저장하면 깨진 파일을 덮어요.

메인테이너의 판단이 필요한 지점

릴리스 워크플로, package.json, 관리 API가 바뀌어서 maintainer-sponsored가 필요해요. 지금 zip은 개발자 서명 없이 묶여서, 맥이 처음 실행을 막아요. 가이드는 그 사실을 말하고, CI에 인증서 서명 훅은 빼 둔 상태예요. 유료 Apple 개발자 계정 없이 이 배포를 받을지 정해 주세요. #5160을 이어서 가져온 거라면 JayYun98 Co-authored-by를 넣을지, attribution-approved로 넘길지 정해 주세요. 위젯이랑 Usage 화면 통합을 이 PR에서 끝낼지, 앱이랑 패키징만 먼저 나눌지도 정해 주세요.

너의 추천

초안인 채로 두세요. 위젯이랑 대시보드가 들어오기 전에, 타임라인 캐시의 취소 신호와 주소 허용, ocx companion이 읽기만 하는데 바꾸는 명령으로 적힌 점, CI의 게이트 문장이랑 문서 맞추기를 먼저 고치세요. #5160은 이미 닫혀 있으니 다시 닫을 일은 없어요.

이 댓글은 grok-bot이 작성했습니다

@devin-ai-integration

Copy link
Copy Markdown
Author

리뷰 반영 (b1c0807, c914ea1):

  • 타임라인 캐시: 첫 요청의 abort 신호 의존 제거, 캐시 키와 그래프 start가 같은 정규화된 now를 사용, 캐시 히트 응답도 req/config를 넘겨 CORS 허용 주소가 동일하게 적용됩니다.
  • other 접기: average/max는 요청 단위 값으로 다시 계산합니다(테스트 추가).
  • ocx companion: show | set <key>=<value> ... | reset 서브커맨드 추가로 mutates: true와 일치합니다.
  • 깨진 companion.jsoncorrupt: true로 응답에 표시하고 PUT 전까지 덮어쓰지 않습니다.
  • Usage 화면 통합은 c914ea18f에 들어갔습니다(별도 탭 없이 Usage 섹션 하나로). 위젯 확장·앱 연동은 이 PR에서 이어서 올라옵니다.
  • CI(구조 게이트 문장, README 해시, 가이드 h3/details, app/** 경로 필터, Co-authored-by)는 구현 완료 후 후행으로 정리합니다.

@lidge-jun

Copy link
Copy Markdown
Owner

추가 리뷰 · 우선순위 60 / 80

지난 리뷰 뒤에 커밋 9개가 붙었어요. 그때 없던 Usage 화면 설정이 들어왔고, 위젯 확장(OpenCodexWidget.appex)이 앱 안에 묶여요. ocx companion은 읽기만 하지 않고 show, set, reset을 해요. 바꾸는 명령으로 적혀 있던 점과 이제 맞아요.

타임라인에서 집었던 곳도 고쳐졌어요. 캐시는 먼저 연 연결의 취소 신호를 다음 요청에 넘기지 않아요. 캐시에서 꺼낸 응답은 이번 요청이랑 설정을 같이 넘겨요. 그래프 시작은 시간 칸에 맞춘 시각을 써요. 줄이 24개를 넘어서 other로 접을 때, 평균이랑 최댓값은 이미 합친 숫자를 또 더하지 않아요. 설정 파일이 깨져 있으면 corrupt: true를 주고, 읽기만으로는 파일을 안 덮어요.

test 1/4부터 test 4/4, macos app은 통과예요. macos 1/2는 이 글을 쓰는 동안 아직 돌고 있어요. hygiene이랑 enforce-targetunsponsored_surface로 그대로 실패예요.

app/Package.swift - 위젯이 시작되는 문을 _NSExtensionMain으로 고정했어요. 그래서 app/Sources/OpenCodexWidget/main.swiftOpenCodexWidgetBundle.main()을 부르는 코드는 실행되지 않아요. 그 묶음에 @main이 없고, app/Widget-Info.plist에도 시작 클래스가 없어요. 빌드는 통과해도, 맥이 위젯을 켤 때 이 묶음을 못 찾을 수 있어요.

app/Sources/OpenCodexWidget/Views.swift - 스냅샷에 있는 menuTitle을 그리는 곳이 없어요. 작은 위젯은 항상 오늘 요청 수를 보여요. 화면 문구는 위젯이 메뉴막대에서 고른 숫자를 따라간다고 적혀 있어요.

gui/src/pages/usage-companion-panel.tsx - 서버가 corrupt: true를 줘도 패널이 그 값을 안 읽어요. 스위치를 하나 바꾸면 설정 전체를 저장해서, 깨진 파일을 기본값으로 덮어요. 메뉴막대 앱의 PollingCoordinatorresponse.corrupt를 버리고 기본 설정만 써요.

메인테이너의 판단이 필요한 지점

릴리스 잡, package.json, 관리 API가 바뀌어서 maintainer-sponsored가 필요해요. 위젯 그림은 앱 그룹이 아니라 위젯 전용 폴더에 써요. 설명은 팀 서명 없이는 앱 그룹이 안 된다고 해요. 개발자 서명 없이 그 폴더가 위젯에 보이는지, 유료 Apple 계정으로 앱 그룹을 쓸지 정해 주세요.

너의 추천

위젯이 목록에 뜨는지 먼저 확인하세요. 안 뜨면 @main이 실행되게 하거나, plist에 시작 클래스를 넣으세요. Usage 화면은 파일이 깨졌다고 보여주고, 사용자가 확인하기 전에는 저장으로 덮지 마세요. 위젯이 메뉴막대 숫자를 따른다면 menuTitle을 그리세요. 오늘 수치만 보여줄 거면 문구를 고치세요. 스폰서 표시 없이는 게이트가 계속 막혀요.

이 댓글은 grok-bot이 작성했습니다

@devin-ai-integration

Copy link
Copy Markdown
Author

Re #5744903890 — addressed in 69cb201b8 / 9e2260e28:

  • _NSExtensionMain / @main — this is the fix that made the widget appear, not a regression. Before -e _NSExtensionMain the appex registered with pluginkit but chronod's spawned process exited 0 before WidgetKit connected, so the gallery never listed it. With the entry point set, chronod archives systemSmall/systemMedium/systemLarge placeholders and all three families render live data (screenshots in the description: gallery + Notification Center). WidgetKit's extension point resolves the WidgetBundle from the module the same way Apple's own widget appexes do (they also expose _NSExtensionMain as the entry and no NSExtensionPrincipalClass). main.swift stays only so the executable target compiles.
  • menuTitle not rendered in the widget — the copy was wrong, not the widget. usage.companion.footer now says the widget shows today's requests / tokens / cost plus the configured chart (all 10 locales). Rendering the menu-bar number in the widget is a follow-up if wanted; the snapshot already carries it.
  • corrupt: true ignored in the GUI — the panel now shows a warning, disables every control and suppresses autosave while the file is corrupt, and offers an explicit "Replace with defaults" action (PUT { reset: true }) before anything is written. The app's PollingCoordinator only reads; it never writes companion.json, so falling back to defaults there is the intended behaviour.
  • maintainer-sponsored — needs @lidge-jun; the touched surfaces are .github/workflows/ci.yml (gate list + app/** path filter), release.yml, package.json and the two new management routes.

@lidge-jun

Copy link
Copy Markdown
Owner

추가 리뷰 · 우선순위 56 / 80

지난 리뷰 뒤에 커밋이 두 개예요. 69cb201은 깨진 설정 파일을 Usage 화면에 보여주고, 위젯 설명을 실제 그림에 맞췄어요. 9e2260e는 그 설정 칸을 fieldset으로 바꾸면서 생긴 테두리를 지웠어요.

파일이 깨져 있으면 경고가 뜨고 스위치는 잠겨요. 스위치를 건드려도 저장하지 않아요. "기본값으로 교체"를 눌러야 서버가 기본값으로 파일을 다시 써요. 위젯 문장은 이제 메뉴막대 숫자를 따른다고 하지 않아요. 오늘 요청, 토큰, 비용, 그리고 여기서 고른 그래프를 보여 준다고 해요. 작은 위젯도 오늘 요청 수를 그려요. menuTitle은 여전히 화면에 없지만, 문장을 고른 쪽이라 지난번의 어긋남은 풀렸어요.

test 1/4부터 test 4/4, macos app은 통과예요. macos 1/2macos 2/2는 아직 돌고 있어요. 69cb201hygieneunsponsored_surface로 실패했어요. 이 팁의 hygieneenforce-target은 작업이 생기기 전에 취소됐어요. 라벨은 intake: hygiene-blocked예요.

app/Package.swift - 위젯이 켜지는 문은 그대로 _NSExtensionMain이에요. app/Sources/OpenCodexWidget/main.swiftOpenCodexWidgetBundle.main()을 부르는 줄은 실행되지 않아요. 그 묶음에 @main이 없고, app/Widget-Info.plist에도 시작 클래스가 없어요. 이번 두 커밋은 이 파일을 안 고쳤어요.

app/Sources/MenuBarCore/PollingCoordinator.swift - 메뉴막대 앱은 corrupt를 아직 안 봐요. 깨진 파일도 정상 기본값처럼 보여요. 설정은 읽기만 해서, 앱이 그 파일을 덮어쓰지는 않아요.

메인테이너의 판단이 필요한 지점

릴리스 잡과 관리 API가 바뀐 상태라 maintainer-sponsored가 필요해요. 위젯 그림을 위젯 전용 폴더에 둘지, 유료 Apple 계정의 앱 그룹을 쓸지는 그대로 정해지지 않았어요.

너의 추천

Usage 화면의 깨진 파일 처리는 이대로 두세요. 위젯이 목록에 뜨는지 먼저 확인하세요. 안 뜨면 @main이 실행되게 하거나, plist에 시작 클래스를 넣으세요. 메뉴막대에도 파일이 깨졌다고 보여줄지는 정해 주세요. 스폰서 표시가 없으면 게이트는 계속 막혀요.

이 댓글은 grok-bot이 작성했습니다

@lidge-jun

Copy link
Copy Markdown
Owner

추가 리뷰 · 우선순위 53 / 80

지난 리뷰 팁 9e2260e 뒤에 커밋이 9개 붙었어요. 메뉴막대 기본 숫자를 요청이 아니라 토큰으로 바꿨고, 토큰은 12M / 37B처럼 소수점 없이 짧게 보여 줘요. Usage 화면에 설치 안내 카드가 생겼어요. 메뉴막대 앱이 OpenCodexMenuBar/… User-Agent로 설정을 읽으면 companion.lastSeenAt이 갱신되고, 10분 안이면 “연결됨”으로 보여 줘요. 차트에 올릴 모델은 스크롤 목록과 스위치로 고르고, 제공자·합계 순으로 정렬해요. 맥 OS 26에서는 팝오버에 Liquid Glass를 쓰고, 작은 위젯 큰 숫자도 오늘 토큰으로 맞췄어요. 베이스는 여전히 dev예요.

초안이고 라벨은 intake: hygiene-blocked예요. hygieneenforce-target은 실패예요. 베이스랑 합치면 CONFLICTING(dirty)라서 지금은 깨끗이 못 합쳐요. 위젯이 켜지는 문은 지난번과 같이 _NSExtensionMain인데, OpenCodexWidgetBundle.main()을 부르는 줄은 그대로 실행되지 않아요.

app/Sources/MenuBarUI/Views.swift - 오늘 칸 순서를 TOKENS → REQUESTS → COST로 바꿨는데, columns[0](토큰) 접근성 라벨은 아직도 “N requests” / “partly estimated”예요. 추정 표시 ~는 요청 칸에만 붙고, 보이스오버 말은 토큰 칸에 요청 이야기를 해요.

docs-site/src/content/docs/guides/macos-menu-bar.md - 영어만 “기본은 토큰”, “macOS 26 Liquid Glass” 문단이 들어갔어요. ja / ko / zh-cn / ru 가이드에는 같은 내용이 없어요. 예전에 구조·해시 검사에 걸린 자리라, 영어만 늘리면 다시 어긋날 수 있어요.

app/Package.swift / app/Sources/OpenCodexWidget/main.swift / app/Widget-Info.plist - 위젯 시작 경로는 그대로예요. _NSExtensionMain으로 들어가고, 묶음에 @main이 없고 plist에도 시작 클래스가 없어요. 빌드가 통과해도 위젯이 안 살아날 수 있는 점은 이전 리뷰와 같아요.

app/Sources/MenuBarUI/PopoverPanel.swift - NSGlassEffectView#available(macOS 26, *)로 감쌌어요. 그래도 SDK에 그 타입이 없으면 컴파일이 깨져요. 패키지 최소 버전은 아직 macOS 13이에요. CI/릴리스가 쓰는 Xcode가 26 SDK인지 확인이 필요해요.

메인테이너의 판단이 필요한 지점

토큰을 정수로만 줄이는 건 의도예요. 12.4M12M이 되고 36.5B37B가 돼요. 메뉴막대·위젯에서는 짧아서 좋고, Usage 차트 축도 같은 규칙을 써요. 더 자세한 숫자가 필요하면 다시 소수점을 살릴지 정하면 돼요.

위젯 시작 문을 고치지 않은 채로 기능만 늘렸어요. Liquid Glass·토큰 기본값·설치 카드는 있어도, 위젯이 실제로 안 뜨면 제목의 “widget” 약속은 아직 못 지킨 거예요. 초안을 유지한 채 위젯 엔트리를 먼저 고칠지, 아니면 위젯을 다음 PR로 빼서 메뉴막대+Usage만 합칠지 정해야 해요.

베이스 dev와 충돌이 있어요. 기능 리뷰보다 리베이스/충돌 해결이 먼저일 수 있어요. #5160을 이 PR이 대체한다고 적혀 있으니, 그 쪽이 열려 있으면 닫을지 확인하면 돼요.

너의 추천

  1. Views.swift에서 토큰 칸과 요청 칸의 접근성 라벨을 칸 순서에 맞게 고치세요. 추정 ~ 설명은 요청 칸에만 두세요.
  2. 위젯 시작을 고치세요. @main 있는 WidgetBundle이 실제로 돌게 하거나, plist에 시작 클래스를 넣고 _NSExtensionMain과 맞춰 주세요. main.swiftOpenCodexWidgetBundle.main()이 죽은 코드로 남지 않게 하세요.
  3. dev에 리베이스해서 충돌을 풀고, 영어 가이드에 넣은 문단을 ja/ko/zh-cn/ru에도 같은 구조로 맞추세요.
  4. macOS 빌드가 돌아가는 Xcode/SDK에서 NSGlassEffectView 컴파일을 한 번 확인하세요. 옛 SDK면 가드만으로는 부족해요.
  5. 위젯이 갤러리에서 실제로 뜨는 것까지 확인하기 전에는 Ready로 올리지 마세요. 초안 유지는 맞아요.

이 댓글은 grok-bot이 작성했습니다

@devin-ai-integration

Copy link
Copy Markdown
Author

Addressed in bd1fba1 (rebased onto current dev, conflicts resolved — mergeable should be clean now):

  1. Views.swift — TODAY accessibility labels follow the column order: tokens column reads "N tokens", requests column carries "N requests[, partly estimated]", cost column "… estimated cost".
  2. Widget entry — the appex does render (gallery + Notification Center screenshots in the description are from the packaged .appex). _NSExtensionMain is the entry (nm: U _NSExtensionMain), NSExtensionPointIdentifier = com.apple.widgetkit-extension, and WidgetKit discovers the WidgetBundle through the extension point — a principal class is not part of the WidgetKit contract. The dead OpenCodexWidgetBundle.main() call in main.swift is removed; the file is comment-only so SwiftPM keeps the executable target.
  3. Guide paragraphs (tokens-by-default, Liquid Glass) added at the same position in ja / ko / ru / zh-cn; README/guide parity tests pass.
  4. NSGlassEffectView branch is now behind #if compiler(>=6.2) in addition to #available(macOS 26, *), so an older Xcode SDK on macos-latest compiles the material fallback. Package minimum stays macOS 13.
  5. Integer-only token abbreviation is intentional (user request); PR stays draft until maintainer-sponsored is applied.

@lidge-jun

Copy link
Copy Markdown
Owner

추가 리뷰 · 우선순위 46 / 80

지난 리뷰 팁 뒤에 커밋이 하나예요. bd1fba1dev에 다시 올려 충돌을 풀었고, 앞에서 짚은 네 가지를 손봤어요. 오늘 칸 보이스오버는 토큰·요청·비용 순서에 맞게 말해요. 영어 가이드에만 있던 “기본은 토큰”, “macOS 26 Liquid Glass” 문단은 ja / ko / ru / zh-cn에도 같은 자리에 넣었어요. 유리 효과는 컴파일러 6.2 이상일 때만 쓰고, 아니면 예전 팝오버 재질로 가요. 위젯 main.swift에 있던 죽은 OpenCodexWidgetBundle.main() 호출은 지우고, 폴더만 채우는 주석으로 바꿨어요. 베이스는 여전히 dev이고, 지금은 합칠 수 있는 상태(MERGEABLE)예요. #5160은 이미 닫혀 있어요.

초안이고 라벨은 intake: hygiene-blocked예요. hygieneenforce-targetunsponsored_surface로 실패해요. test 1/4~4/4, gates, docs, structure는 통과예요. macos app / macos 1/2 / macos 2/2는 아직 돌고 있어요.

app/Sources/MenuBarUI/Views.swift - 지난번에 틀렸던 접근성 라벨은 맞춰졌어요. 토큰 칸은 “N tokens”, 요청 칸만 추정(partly estimated)을 말해요.

docs-site/src/content/docs/{ja,ko,ru,zh-cn}/guides/macos-menu-bar.md - 영어와 같은 문단이 들어갔어요. 구조·해시 검사에 다시 걸릴 여지는 줄었어요.

app/Sources/MenuBarUI/PopoverPanel.swift - NSGlassEffectView#if compiler(>=6.2)#available(macOS 26, *) 둘 다로 막았어요. 옛 Xcode에서는 재질 쪽으로만 컴파일돼요.

app/Sources/OpenCodexWidget/main.swift / app/Package.swift - 시작 문은 그대로 _NSExtensionMain이에요. 묶음에 @main은 없고, plist에도 시작 클래스는 없어요. 작성자는 갤러리·알림 센터 스크린샷으로 위젯이 뜬다고 했고, Package.swift에도 chronod 때문에 _NSExtensionMain을 쓴다고 적혀 있어요. 코드만 보면 예전과 같은 문이지만, “죽은 main 호출”은 이제 없어요.

app/Sources/MenuBarCore/PollingCoordinator.swift - 메뉴막대 쪽은 설정 파일이 깨져도 corrupt를 아직 안 보여 줘요. Usage 화면만 경고하고, 앱은 읽기만 해서 파일을 덮어쓰지는 않아요. 이번 커밋이 안 건드렸어요.

메인테이너의 판단이 필요한 지점

릴리스 잡·관리 API·앱 묶음이 바뀐 자리라 maintainer-sponsored가 필요해요. 스폰서 라벨이 없으면 게이트는 계속 막혀요.

위젯 시작을 SPM + _NSExtensionMain으로 두는 걸 이 PR에서 받아들일지, 아니면 @main WidgetBundle을 더 분명하게 둘지 정하면 돼요. 작성자 증거(스크린샷)와 macos app CI가 통과하면 기능 쪽은 충분해 보여요.

메뉴막대에도 깨진 설정 경고를 넣을지는 선택이에요. 지금처럼 Usage만 보여 줘도, 앱이 파일을 고치지 않아서 위험은 작아요.

너의 추천

  1. maintainer-sponsored를 붙인 뒤 초안을 Ready로 올리세요. 그 전에는 Ready로 올리지 마세요.
  2. macos app이 초록이 되는 것까지 보고, 위젯이 갤러리에 실제로 남는지만 한 번 더 확인하세요. 뜨면 _NSExtensionMain 문은 이대로 두세요.
  3. 접근성·다국어 가이드·유리 효과 가드는 이대로 두세요.
  4. 메뉴막대 corrupt 표시는 없어도 되고, 넣고 싶으면 다음 PR로 미뤄도 돼요.

이 댓글은 grok-bot이 작성했습니다

@lidge-jun lidge-jun added the maintainer-sponsored Maintainer sponsors this change to an auth, workflow, release, or dependency surface label Sep 20, 2026
@github-actions github-actions Bot removed the intake: hygiene-blocked Deterministic PR hygiene checks failed label Sep 20, 2026
@github-actions
github-actions Bot marked this pull request as ready for review September 20, 2026 03:33
Nine numbered docs covering the roadmap for a maintainer-owned macOS menu
bar app in app/, consolidating the two competing community PRs (#387
Swift/SwiftUI, #421 Tauri/React).

- 000_plan: constraints, dependency-ordered phase map, accept criteria
- 001_pr_survey: head-to-head of both PRs; stack decision is Swift/AppKit
  runtime with HTTP management-API transport, plus the salvage list
- 002_api_surface: live payload inventory, the seconds-vs-milliseconds
  quota timestamp trap, and the default-provider 400 trap
- 003_design_read: Design Read and dial lock (V2/M1/D7), inheriting the
  existing gui/src/styles.css tokens
- 010-050: diff-level decade docs, one per implementation phase
Adversarial Phase-0 review returned FAIL. Corrections, all verified against
live source and the running proxy:

- /api/stop calls stopServiceIfInstalled() before responding, so nothing
  restarts the proxy. The app now ships Stop proxy, never Restart, and
  never spawns a process.
- /api/usage supports only 7d/30d/all; 24h silently degrades to 30d. The
  range is now a closed enum and the UI labels the range the response
  returned, not the one it requested.
- defaultProvider lives on /api/config, not /api/settings. Added the model,
  the client method, and the test.
- The bundle script now defines every path before use and copies Info.plist
  before plutil; it could not have run as previously written.
- release.yml grants contents:write and id-token:write at workflow level, so
  the new jobs declare explicit least-privilege permissions. Added a separate
  attach-macos job so packaging can never block the npm publish, and pinned
  both new actions to full SHAs.
- Re-surveyed PR #421 at head 049ef2a: the committed src-tauri/target tree
  was already removed by the contributor. The closing comment must credit
  that fix rather than repeat a stale defect.
- /api/logs exists for per-request activity; documented as a deliberate v1
  exclusion instead of an implicit gap.
- Phase 1 no longer claims verification via a Phase 4 script.
- Added StartupHealth service fields, security-review acceptance evidence to
  Phase 4, and removed developer-absolute paths from tracked docs.
…hip, plist)

Round-2 adversarial review returned FAIL on 9 findings, most of them caused
by round-1 edits that corrected prose without correcting the specs those
documents actually lock.

- 003 was never touched in round 1, so the design lock still mandated a 24h
  sparkline and a Restart button that 002/030 prove impossible. Wireframe now
  shows LAST 7 DAYS and Stop proxy.
- release.yml's input is named dry-run, not dry_run. inputs.dry_run would
  resolve to null and the attach-macos guard would silently pass during a
  dry run — the exact failure that guard exists to prevent.
- Bundle ownership was relocated, not resolved: Phase 2 claimed a launchable
  .app while the builder stayed in Phase 4. Phases 1-3 now verify through
  swift test/build/run; Phase 4 owns the bundle end to end.
- app/Info.plist was missing CFBundleExecutable, CFBundlePackageType, and
  CFBundleIconFile, so the specified bundle would not have launched.
- Re-read #421 at head 049ef2a: menubar/src/api.ts:12-13 returns the token
  into renderer memory, so the PR's isolation claim does not hold. Removed
  that credit from both the survey and the planned closing comment, and
  removed the remaining stale rejection sentences.
- Scoped the absolute-path criterion to files this unit touches; unrelated
  historical devlogs already contain such paths.
- loading is now explicitly exempt from the next-action rule, and per-section
  empty states are defined with their own copy.
- The range-fallback test injects a stubbed response, since the closed
  UsageRange enum makes the curl path unreachable from production code.
Round-3 audit returned GO-WITH-FIXES (blockers=0). Cleared all three:

- 010 still called the first bundle a Phase-2 deliverable, contradicting
  000/020/040. Phase 4 owns the bundle end to end.
- 001's salvage list still credited #421 with renderer-side token isolation,
  contradicting its own verified analysis, and claimed all four surfaces were
  adopted when per-request activity was deliberately excluded.
- 040 and 050 still scoped the absolute-path rule to every tracked file, which
  pre-existing historical devlogs already violate. Both now match 000's
  unit-scoped wording.
Phase 1 of the macOS companion (010_phase1_core.md). Zero third-party
dependencies; AppKit and Foundation only.

- Discovery resolves the proxy from OPENCODEX_HOME/runtime-port.json with a
  10100 fallback. The host is pinned to loopback and never read from the
  record, so a file write cannot redirect the app at another host.
- ProxyModels mirror the live payloads. QuotaReport.normalized() absorbs two
  real traps: the window key differs per provider (weekly/monthly/custom),
  and weeklyResetAt arrives in seconds from openai but milliseconds from
  anthropic within the same array, so timestamps are disambiguated by
  magnitude.
- UsageRange is a closed enum because the server silently degrades an
  unrecognized range to 30d; UsageReport.rangeLabel is derived from the
  response so the UI can never label 30 days of data as something else.
- ProxyClient is an actor. ProxyError carries human sentences only, never a
  response body, since bodies can echo configuration.
- Format renders an em dash for unknown and a real zero for zero; the live
  proxy reports 3.6e10 tokens, so everything is abbreviated.

Testing is an executable target rather than a .testTarget: Command Line
Tools resolves neither XCTest (module not found) nor the swift-testing
runtime (Testing.framework fails to dlopen). Requiring full Xcode to run
these tests would exclude most contributors. 31 cases pass via
`swift run --package-path app MenuBarCoreTests`.

Verified live against the running proxy: endpoint discovery, health
(at-risk, service-managed), defaultProvider=openai from /api/config, 7-day
usage (44.5K requests, 7.34B tokens, $6.15K), and four provider quotas with
correctly resolved reset windows.
Adversarial review of c7fbf57 returned FAIL on 10 findings. All verified
against the live proxy or Apple docs before folding.

- Info.plist: add NSAllowsLocalNetworking. macOS 14 stopped allowing IP
  loads under ATS, so the packaged bundle could not reach 127.0.0.1 at all
  while swift run stayed green — the app's primary function, broken only in
  the artifact users would actually download.
- ProxyClient: wire the lazy Keychain retry that the plan required and the
  code never implemented. A CredentialStore protocol is injected, the key
  loads once, and exactly one retry follows a 401 so a stale key cannot spin.
- Keychain: set kSecUseDataProtectionKeychain on every query, without which
  kSecAttrAccessible is ignored on macOS; tighten to ThisDeviceOnly; update
  before add so a failed add cannot destroy a working key.
- ProxyModels: live kimi reports fiveHourPercent alongside weeklyPercent, and
  cursor and google-antigravity each carry two customWindows. Added the
  five-hour fields and normalizedWindows() returning every window;
  normalized() keeps an explicit longest-horizon precedence.
- isEmptyOrUnknown preserves three states so an omitted request count cannot
  render as "No requests".
- Cancellation now propagates instead of reading as a stopped proxy, and
  unrelated transport failures get their own .transport case.
- ProxyEndpoint is failable; baseURL is built once instead of force-unwrapped.
- Format promotes at rollover: 999_999 renders 1.00M, not 1000K.
- TransportSuite adds 14 cases over status mapping, the 401 retry path,
  cancellation, request shape, and body redaction. 51 pass, 0 fail.
- Harness no longer counts a case as passed when it recorded a failure.

Live re-verification across all six providers: Kimi 5h+week, Cursor's three
windows, and correct primary-window selection for each.
lidge-jun and others added 2 commits September 20, 2026 12:37
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@github-actions
github-actions Bot marked this pull request as draft September 20, 2026 03:43
The branch opened this unit under devlog/_plan/ before dev published the
same unit as closed at devlog/_fin/260725_macos_menubar_app/ in 8ae52e4.
The rebase replayed the _plan addition on top of that publication, so the
tree carried both copies: nine files, 2,605 lines, six byte-identical to
their _fin counterparts.

The three that differ are worse than redundant. 003_design_read.md,
010_phase1_core.md and 020_phase2_ui.md keep the decimal token text
(12.4M, 36.5B) that this same pull request corrects to integers in the
_fin copies, so the duplicate contradicted the corrected record two
directories over.

AGENTS.md defines _plan as units still open and _fin as closed work, and
nothing in CI reads devlog/ - the file-size scanner excludes it - so no
gate would have caught this. The _fin copies, including the
051_feature_summary.md this PR adds, remain the record.
…artifact pin

Two defects in the release jobs this pull request adds, both found in
review of the workflow surface.

package-macos had no needs:, so a dispatch that validate-dispatch would
reject still spun up a macOS runner and packaged an asset. Every other
job in the file gates on that validation; this one now does too. The
blast radius was bounded - contents: read, no secrets, and the script's
own version guard - but running at all on a rejected dispatch is not the
design.

The upload step pinned actions/upload-artifact at v5.0.0 while ci.yml
already pins v7.0.1, leaving the repository with two pins for one
action and pairing a v5 upload against the v8 download in attach-macos.
Both now use the SHA ci.yml already trusts, which is also the pairing
actions/download-artifact v8 expects.
@lidge-jun

Copy link
Copy Markdown
Owner

추가 리뷰 · 우선순위 40 / 80

지난 리뷰 팁(bd1fba1) 뒤에 커밋이 두 개예요. 72063f73은 닫힌 macOS 유닛의 _plan 복사본을 지웠어요. dev에 이미 _fin/260725_macos_menubar_app/이 있는데 리베이스로 _plan이 다시 살아나서, 같은 문서가 두 군데에 있었고 일부는 정수 토큰 표기까지 서로 달랐어요. 지금은 _fin만 남아요. a4f084af는 릴리스 쪽이에요. package-macosvalidate-dispatch를 기다리게 됐고, actions/upload-artifact 핀이 ci.yml과 같은 v7.0.1 SHA로 맞춰졌어요. 예전에 v5 업로드와 v8 다운로드가 짝이 안 맞던 구멍이에요. 라벨에 maintainer-sponsored가 붙었고, 베이스는 dev, 합칠 수 있는 상태(MERGEABLE)예요. 아직 초안(draft)이고, 이번 푸시 기준 hygiene / enforce-target / macOS·Windows 잡 등은 아직 돌아가는 중이에요. #5160은 이미 닫혀 있어요.

.github/workflows/release.yml - package-macosneeds: validate-dispatch가 생겼어요. 거절된 dispatch에서도 macOS 러너가 돌던 문제는 막혔어요.

.github/workflows/release.yml - upload-artifact043fb46… (v7.0.1)로 ci.yml과 같아요. attach-macosdownload-artifact v8과도 짝이 맞아요.

devlog/_plan/260725_macos_menubar_app/ - 디렉터리가 통째로 사라졌어요. 기록은 devlog/_fin/260725_macos_menubar_app/만 남아요. 정수 토큰 예시가 서로 싸우던 상태도 끝났어요.

메인테이너의 판단이 필요한 지점

초안을 Ready로 올릴 타이밍이에요. maintainer-sponsored는 이미 있어요. 이번 푸시 CI(특히 hygiene, macOS 앱/런치드)가 초록이 된 뒤에 올리는 게 안전해요.

위젯을 SPM + _NSExtensionMain으로 두는 건 지난번에 이어서, 갤러리에 실제로 남는지만 한 번 더 보면 돼요. 코드만의 새 문제는 없어요.

메뉴막대에 깨진 설정(corrupt) 경고를 넣을지는 여전히 선택이에요. Usage만 보여 줘도 앱이 파일을 덮어쓰지 않아서 위험은 작아요.

너의 추천

  1. 이번 푸시 CI가 다 초록이면 초안을 Ready로 올리세요. 스폰서 라벨은 이미 있어요.
  2. 릴리스 needs / artifact 핀 / _plan 정리는 이대로 두세요. 다시 손대지 마세요.
  3. 위젯은 갤러리·알림 센터에 남는지만 확인하세요. 뜨면 _NSExtensionMain은 유지하세요.
  4. 메뉴막대 corrupt 표시는 다음 PR로 미뤄도 돼요.

이 댓글은 grok-bot이 작성했습니다

@github-actions github-actions Bot added intake: hygiene-blocked Deterministic PR hygiene checks failed review-ready and removed intake: hygiene-blocked Deterministic PR hygiene checks failed labels Sep 20, 2026
@github-actions
github-actions Bot marked this pull request as ready for review September 20, 2026 03:56
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

# Conflicts:
#	scripts/test-layout/layout.json
#	tests/fixtures/test-layout-expected.json
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 20, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-20T11:10:14.042161Z 9eb033e Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions
github-actions Bot marked this pull request as draft September 20, 2026 11:02
Both sides register new test files in the layout maps; the resolution is the union of the two entry sets, not a choice between them.
@lidge-jun
lidge-jun marked this pull request as ready for review September 20, 2026 11:04
@lidge-jun
lidge-jun merged commit 38a5ab9 into dev Sep 20, 2026
33 of 35 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9eb033e19b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

aggregation: settings.aggregation,
grouping: settings.chartGrouping,
});
if (settings.models?.length) query.set("models", settings.models.join(","));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve an explicit empty model selection

When the user turns off the final model, toggleCompanionModels persists models: [], but this condition omits the query parameter and the server interprets an omitted model filter as “all models.” The chart therefore repopulates with every series even though the UI reports zero selected; the Swift client repeats the same omission in ProxyClient.timeline. Preserve an explicit no-model state across the client/API contract instead of dropping the empty array.

AGENTS.md reference: gui/AGENTS.md:L9-L10

Useful? React with 👍 / 👎.

case .requests: rendered = Format.count(summary?.requests)
case .tokens: rendered = Format.tokens(summary?.totalTokens)
case .cost: rendered = Format.cost(summary?.estimatedCostUsd)
case .quota: rendered = Format.percent(quotas.compactMap(\.percent).min())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Show the highest quota pressure instead of the lowest

When multiple providers report quotas, selecting .min() makes the headline show the least-used provider—for example, 10% while another provider is at 99% and about to block requests. QuotaReport.normalized() explicitly defines larger percentages as greater pressure, so this reduction and the equivalent one used for {quotaPercent} should use the maximum; the medium widget has the same inverted selection in OpenCodexWidget/Views.swift.

Useful? React with 👍 / 👎.

Comment on lines +240 to +242
if (popoverOpen || snapshot.settings.menuBarMetric == .quota), let quotas = try? await client.quotas() {
guard isCurrent(cycle) else { return false }
snapshot.quotas = quotas

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain quota refreshes while the popover is closed

When menuBarMetric is quota and the popover is closed—the normal startup and steady-state case—the preceding condition fetches quotas, but this guard calls isCurrent, which additionally requires popoverOpen. The successful result is discarded before updating snapshot.quotas, leaving the quota headline unavailable until the panel is opened and stale after it closes; this guard should only verify that the refresh generation is current.

Useful? React with 👍 / 👎.

Comment on lines +166 to +168
private func openCompanionSettings() {
guard let url = URL(string: "\(endpoint.baseURL.absoluteString)/#/usage#usage-section-companion") else { return }
NSWorkspace.shared.open(url)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a route-compatible companion settings deep link

Selecting “Companion settings…” constructs a fragment containing a second #, so the dashboard router receives usage#usage-section-companion rather than the supported usage route. resolveAppHashChange treats that value as unknown and normalizes it to the dashboard, meaning this menu action does not open either Usage or the companion section; add a supported sub-route/scroll mechanism instead of appending an HTML fragment inside the hash route.

Useful? React with 👍 / 👎.

lidge-jun added a commit that referenced this pull request Sep 20, 2026
Consolidates the desktop stack that was carried as a GitHub native stack on top
of the macOS menu bar companion. #5196 landed as 38a5ab9 by squash, which
detached every child in the chain from its base, so the remaining work is
applied here as one branch against the current dev instead of replayed through
bases that no longer exist.

Carries the standalone binary, the Tauri v2 cross-platform tray and webview
shell, the GUI desktop shell integration, the WidgetKit appex bundle, and the
signed desktop packaging for DMG, MSI, AppImage and deb.

Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@lidge-jun
lidge-jun deleted the feat/macos-app branch September 20, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request maintainer-sponsored Maintainer sponsors this change to an auth, workflow, release, or dependency surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants