Skip to content

feat(desktop): bundle WidgetKit appex into the Tauri app, Rust snapshot writer, retire Swift menu bar (desktop stack 4/5) - #5299

Closed
devin-ai-integration[bot] wants to merge 6 commits into
devin/1789890042-gui-desktop-shellfrom
devin/1789891915-widget-bundle
Closed

devin-ai-integration[bot] wants to merge 6 commits into
devin/1789890042-gui-desktop-shellfrom
devin/1789891915-widget-bundle

Conversation

@devin-ai-integration

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

Copy link
Copy Markdown
Contributor

Desktop stack 4/5 — stacked on #5287 (base: devin/1789890042-gui-desktop-shell). Series: #5256#5260#5287 → this → packaging/updater (5/5).

Summary

The Tauri shell now owns the whole macOS companion: it writes the WidgetKit snapshot and bundles the native OpenCodexWidget.appex, so the standalone Swift menu bar app is retired. WidgetKit itself stays Swift (it must be a native extension).

  • Rust snapshot writerdesktop/src-tauri/src/widget.rs (macOS only; no-op elsewhere). Mirrors WidgetSnapshot schema v1 byte-for-byte (camelCase, state/stateTitle/detail, today, quotas, chart{start,bucketSeconds,style,series≤6}, lastUpdated, generatedAt); state mapping follows ProxySnapshot (running/unreachable/unauthorized/degraded with the same titles/details). menuTitle reuses tray::render_title, so the widget shows exactly what the tray shows. Written to ~/Library/Containers/com.opencodex.desktop.widget/Data/Library/Application Support/OpenCodex/snapshot.json via .tmpchmod 0600rename, skipped when unchanged modulo generatedAt; first write at tray install, then every 5th 60 s tray tick. The widget's own .after(5 min) timeline policy picks it up — no WidgetCenter reload needed. New ProxyClient::{startup_health, usage_today, timeline}.
  • Appex in the .appdesktop/scripts/build-widget.sh builds the Swift product (universal lipo by default), stages desktop/src-tauri/widget/OpenCodexWidget.appex (gitignored) with Widget-Info.plist, syncs CFBundleShortVersionString/CFBundleVersion from tauri.conf.json, and ad-hoc signs with Widget.entitlements (MACOS_SIGN_IDENTITY branch kept for 5/5). tauri.conf.json maps it via bundle.macOS.files → PlugIns/OpenCodexWidget.appex. Widget bundle id → com.opencodex.desktop.widget (plist + WidgetSnapshotStore default).
  • Retire the Swift menu bar app — removed MenuBarApp, MenuBarUI, MenuBarUITests, UIProbe, IconProbe, app/Info.plist, scripts/build-macos-app.sh, scripts/package-macos-release.sh, tests/gui/macos-build-script.test.ts (+ layout entries). Package renamed OpenCodexWidget; MenuBarCore + MenuBarCoreTests kept as the widget's model/formatting layer (trimming is a follow-up). CI macos-appwidget (core tests + appex build + codesign -dv). release.yml: package-macos/attach-macos removed; attach-standalone keeps attaching the standalone-* assets. Release gap: between this PR and 5/5 no macOS companion asset is produced; the Tauri DMG/MSI/AppImage/deb land in 5/5.
  • timeline_query percent-encodes model ids; readme/i18n-manifest.json resynced (review follow-up, c44c370).
  • ocx tray deprecated, not removed — CLI summary/details note that the desktop app supplies the tray on all three OSes; PS1 tray keeps working for installs without the app. Skill surface regenerated.
  • Docs: AGENTS.md, structure/overview.md, structure/desktop-shell.md (widget snapshot section), desktop/README.md, macOS guide (5 locales), CLI lifecycle reference (8 locales), READMEs.

Verification

  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test (10 passed; snapshot serialization vs Swift field names, 4 error-state mappings, write-if-changed ignoring generatedAt, series truncation).
  • bash desktop/scripts/build-widget.sh on macOS → codesign -dv OK (identifier com.opencodex.desktop.widget), lipo -archsx86_64 arm64, plist version 2.61.0.
  • swift run --package-path app MenuBarCoreTests (118 passed).
  • bun run typecheck, bun run structure:check, bun run skill:surface:check, git diff --check.
  • bun test tests/ci-workflows release/ci-gate/skill files (162 + 5 + 16 passed), test-layout + tooling (23 passed).
  • GUI lint, lint:i18n; docs-site build (481 pages).
  • Not yet: a full tauri build with the appex embedded and a widget-gallery screenshot — that is the packaging pass in 5/5; I'll attach it there.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

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

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.

lidge-jun and others added 5 commits September 20, 2026 01:21
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>
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>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor 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 20, 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: f1ada942-0f4d-4c97-8695-3d7ce2afe11f

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.

@devin-ai-integration
devin-ai-integration Bot added this pull request to stack #5262 September 20, 2026 08:24
@github-actions github-actions Bot added enhancement New feature or request intake: hygiene-blocked Deterministic PR hygiene checks failed labels Sep 20, 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.

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 66 / 80

이 PR은 데스크톱 스택 4/5입니다. 기준 브랜치는 dev가 아니라 #5287 위 스택(devin/1789890042-gui-desktop-shell)입니다. 지금은 예전에 따로 돌리던 Swift 메뉴바 앱을 없애고, Tauri 데스크톱 앱이 macOS 위젯까지 같이 책임지게 만듭니다.

하는 일을 쉽게 말하면 세 덩어리입니다. 첫째, Rust가 프록시 상태를 읽어 snapshot.json을 씁니다. 위젯은 그 파일만 읽고 네트워크는 안 탑니다. 둘째, build-widget.sh로 WidgetKit appex를 만들어 .appPlugIns에 넣습니다. 셋째, 단독 Swift 메뉴바 앱·패키징 스크립트·관련 CI/릴리스 잡을 치웁니다. MenuBarCore는 위젯용으로 남기고, ocx tray는 당장 지우지 않고 “데스크톱 앱이 트레이를 대신한다”고만 적어 둡니다. 문서와 다국어 가이드도 같이 고쳤습니다. 초안(draft)이고, 전체 tauri build+위젯 갤러리 확인은 5/5로 미룬다고 본문에 적혀 있습니다.

라인 - .github/workflows/ci.yml / release.yml / package.json — hygiene가 unsponsored_surface로 막혔고 intake: hygiene-blocked가 붙어 있습니다. 워크플로·릴리스 표면이라 메인테이너가 maintainer-sponsored를 붙여야 게이트가 열립니다.
라인 - tests/ci-workflows/docs-readme-translation-parity.test.ts — test 3/4 실패. README.md를 고친 뒤 readme/i18n-manifest.json과 fr/ko/zh-CN/zh-TW/ru/ja/tr 동기화가 같은 커밋에 없습니다.
라인 - base devin/1789890042-gui-desktop-shell (#5287) — dev 직행이 아닙니다. 아래 스택이 안 들어가면 이 PR도 혼자 머지하면 안 됩니다.
라인 - .github/workflows/release.ymlpackage-macos / 예전 macOS 에셋 attach를 뺍니다. 5/5 Tauri 패키징 전까지 릴리스에 macOS 컴패니언 바이너리가 빠질 수 있습니다.
라인 - desktop/src-tauri/src/widget.rs timeline_querymodels를 그대로 쿼리에 붙입니다. openai/gpt처럼 /가 있으면 URL이 깨질 수 있습니다. 인코딩이 필요합니다.
라인 - desktop/src-tauri/src/widget.rs / tray.rs — 예전 Swift는 WidgetCenter.reload를 호출했는데, 지금은 5분 타임라인 정책만 믿습니다. 상태가 바뀌어도 위젯이 최대 약 5분 늦게 갱신될 수 있습니다.
라인 - app/Widget.entitlements + 스냅샷 경로 — App Group 없이 ~/Library/Containers/com.opencodex.desktop.widget/...에 호스트가 직접 씁니다. 지금 Tauri가 샌드박스 밖이면 동작할 수 있지만, 나중에 호스트가 샌드박스되면 깨지기 쉬운 설계입니다.
라인 - desktop/src-tauri/tauri.conf.json — appex를 PlugIns에 매핑만 하고, 빌드 전에 prepare-widget를 강제하는 훅은 없습니다. 문서 수동 단계에만 의존합니다. 5/5 자동화에서 빠지면 위젯 없는 .app이 나올 수 있습니다.
라인 - #5196 (feat/macos-app) — 같은 macOS 메뉴바+위젯 방향의 옛 PR입니다. 이 스택이 살아남으면 중복으로 닫는 편이 맞습니다.

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

maintainer-sponsored를 지금 붙일지, 아니면 README 동기화·쿼리 인코딩·릴리스 공백 설명을 먼저 고치게 할지. 5/5가 오기 전에 macOS 릴리스 에셋이 잠깐 비는 걸 허용할지. App Group으로 공유 컨테이너를 바꿀지, 컨테이너 경로 직접 쓰기를 당분간 유지할지. #5196을 지금 landed-via/superseded로 닫을지.

너의 추천

방향은 맞습니다. 다만 지금은 머지하지 마세요. ① README i18n 동기화로 test 실패를 고치고, ② models 쿼리 인코딩을 넣고, ③ hygiene는 메인테이너 스폰서 후에만 통과시키고, ④ #5287 → 이 PR → 5/5 순서를 지키세요. 릴리스에서 macOS 에셋이 비는 구간은 PR 본문이나 릴리스 노트에 한 줄로 명시하는 게 좋습니다. #5196은 이 스택이 dev에 안착하면 닫는 쪽을 추천합니다.

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

…fest

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Addressed in c44c370:

  • README parity test — the seven locale READMEs were already resynced; only readme/i18n-manifest.json still carried the old sourceSha256. Updated; docs-readme-translation-parity.test.ts 53/53 green.
  • timeline_query models encoding — model ids are now percent-encoded (openai/gpt-4.1openai%2Fgpt-4.1, commas inside an id → %2C), with a unit test.

Not changed, with reasoning:

  • WidgetCenter.reload latency — the Rust host can't call WidgetKit; the widget's timeline policy is .after(5 min) so a state change shows within ≤5 min, which matches the Swift companion's own 5-minute snapshot cadence. Accepted for now.
  • App Group vs container path — the Tauri host is not sandboxed and the appex reads its own container, so direct writes work today; moving to an App Group needs a Team-ID-prefixed group in both entitlements and belongs with the Developer ID signing work in 5/5.
  • prepare-widget not enforced by tauri build — the enforcement lands in 5/5: the release/CI jobs run prepare-widget before tauri build and assert PlugIns/OpenCodexWidget.appex exists in the produced .app.
  • macOS release asset gap — noted in the PR body: between this PR and 5/5 no macOS companion asset is produced; 5/5 adds the DMG (standalone ocx binaries keep shipping via attach-standalone).
  • feat(macos): menu bar companion + widget, with CodexBar-style usage monitor integrated into Usage #5196 — maintainer call; recommend closing as superseded once the stack lands on dev.

@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • hygiene: unsponsored_surface.

What to do

  • Fix 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.
  • 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.

This pull request was already a draft. Its draft status will be preserved after every issue above is resolved.
@devin-ai-integration[bot] Tick the boxes once your local CI is green, your branch is on the latest dev commit, and every correct Codex and CodeRabbit finding is resolved.

@lidge-jun

Copy link
Copy Markdown
Owner

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

이전 리뷰(95f6829) 이후 커밋 c44c370만 보면, 내가 지적했던 바로 고칠 수 있는 두 가지는 처리됐습니다. readme/i18n-manifest.jsonsourceSha256을 맞춰 README 다국어 패리티 테스트가 다시 통과하고, widget.rstimeline_query는 모델 id를 percent-encode한 뒤 붙이며(openai/gpt-4.1openai%2Fgpt-4.1) 단위 테스트도 넣었습니다. CI에서도 test 1~4/4가 초록입니다. PR 전체 방향(Rust 스냅샷 쓰기 → appex 번들 → 단독 Swift 메뉴바 은퇴)은 그대로이고, 기준 브랜치는 여전히 #5287 스택입니다.

라인 - .github/workflows/ci.yml / release.yml / package.json — hygiene unsponsored_surface와 라벨 intake: hygiene-blocked그대로입니다. 메인테이너가 maintainer-sponsored를 붙이기 전에는 게이트가 안 열립니다.
라인 - base devin/1789890042-gui-desktop-shell (#5287) — dev 직행이 아닙니다. 아래 스택이 안 들어가면 이 PR만 머지하면 안 됩니다.
라인 - .github/workflows/release.yml — macOS 컴패니언 패키징 제거로 생기는 릴리스 공백은 PR 본문에 적혀 있고, 작성자 답변대로 5/5(Tauri DMG)까지 허용 구간으로 잡았습니다. 그래도 5/5가 늦어지면 릴리스에 macOS 앱이 잠깐 없습니다.
라인 - WidgetCenter.reload / App Group / prepare-widget 강제 — 작성자가 5/5 또는 “지금 Tauri는 비샌드박스”로 미룬 항목입니다. 이번 커밋에서 코드로 바뀐 건 없고, 지금 당장 머지 차단 이유는 아닙니다. 다만 5/5에서 실제로 CI가 prepare-widget 후 appex 존재를 검사하는지 확인이 필요합니다.
라인 - #5196 — 여전히 같은 방향의 옛 PR입니다. 스택이 dev에 안착하면 superseded로 닫는 판단이 남습니다.
라인 - draft + readiness 체크리스트 0/4 — 아직 리뷰 준비 완료로 올려지지 않았습니다.

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

워크플로·릴리스 표면을 보고 maintainer-sponsored를 지금 붙일지. #5287#5299 → 5/5 순서를 지키면서 5/5 전까지 macOS 릴리스 에셋 공백을 받아들일지. App Group·서명·prepare-widget 강제를 5/5에만 몰아넣을지. #5196을 지금 닫을지, dev 안착 뒤로 미룰지.

너의 추천

코드 쪽 급한 구멍(README 패리티, 모델 id 인코딩)은 막혔으니 머지 차단 우선순위는 내려갑니다. 그래도 지금은 머지하지 마세요. ① 메인테이너가 워크플로 diff를 보고 스폰서하고, ② #5287이 먼저 들어간 뒤에만 이 PR을 올리고, ③ draft 체크리스트는 로컬/CI 확인 후 작성자가 채우게 하세요. 위젯 갱신 지연·App Group·빌드 훅은 5/5 리뷰에서 한 번에 재확인하면 됩니다.

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

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Confirming the open items now that 5/5 exists (#5309):

No code change on this PR from this review.

@lidge-jun
lidge-jun marked this pull request as ready for review September 20, 2026 11:05
@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:12:32.952458Z c44c370 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.

@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: c44c37047e

ℹ️ 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".

Comment on lines +148 to +150
bun run prepare-sidecar
bun run prepare-widget
bunx tauri build

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 Enter the desktop package before running its scripts

In the documented scenario immediately after cd opencodex, Bun resolves these commands against the root package.json, which defines neither script; checking this exact path makes bun run prepare-sidecar exit with Script not found. Both scripts and the Tauri configuration reside under desktop/, so the documented source build always stops before compilation; add cd desktop or root-level delegating scripts.

AGENTS.md reference: docs-site/AGENTS.md:L7-L10

Useful? React with 👍 / 👎.

cd opencodex
bun run build:macos
bun run prepare-sidecar
bun run prepare-widget

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 Disable universal output for the CLT-only build

Even after running this from desktop/, the guide says the Xcode Command Line Tools are sufficient, but prepare-widget defaults UNIVERSAL=1 and therefore builds both architectures; the same page correctly notes below that this requires full Xcode. A user with only the stated prerequisites therefore hits a linker/toolchain failure, so the local command should pass UNIVERSAL=0 or the prerequisites should require full Xcode.

AGENTS.md reference: docs-site/AGENTS.md:L7-L10

Useful? React with 👍 / 👎.

.unwrap_or_else(|_| json!({ "settings": {} }));
let today = proxy.usage_today().await.ok();
let quota_value = proxy.quotas().await.ok();
let timeline_value = proxy.timeline(&timeline_query(&settings)).await.ok();

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 Honor showChart before fetching a timeline

When companion settings contain showChart: false, this unconditional request still fetches the timeline and make_snapshot serializes it, so the widget continues displaying a chart and performs the expensive usage-log aggregation the user disabled. The replaced Swift coordinator guarded this request with snapshot.settings.showChart; apply the same condition here and leave chart unset when the option is false.

Useful? React with 👍 / 👎.

today: today_snapshot,
quotas: quotas(quotas_value),
chart,
last_updated: timeline_value.map(|_| now_seconds()),

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 Fall back to the usage refresh time

If /api/usage/timeline fails while /api/usage?range=today succeeds, the snapshot contains fresh metrics but lastUpdated is null, causing every widget size to report “Not updated.” The previous Swift snapshot used timelineUpdated ?? usageUpdated; record the refresh time when either timeline or today's usage succeeds rather than tying freshness solely to the optional chart request.

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner

Superseded by #5318, merged to dev as 2ff7f3385d83c05279093eb9c7bdd5d5963d9605, with attribution in the branch commit.

#5196 landed first as 38a5ab9fc4eaa223d3d58982b0bc097279138fca. Squashing the bottom of a native stack detaches every child from its base, so the rest of the chain could no longer be merged through the stack: the base branches stopped resolving and the recorded bases pointed at tips the chain had been rebased away from. Replaying the branches on top of dev reproduced the already-squashed commits and produced 48 conflicts, none of them real disagreements.

The consolidation applies the tree difference of the stack tip against the point the chain branched from. One file genuinely conflicted and both intents were kept. Everything from the standalone binary through the signed packaging is on dev.

Closing as superseded rather than stale.

@lidge-jun lidge-jun closed this Sep 20, 2026
@lidge-jun
lidge-jun deleted the devin/1789891915-widget-bundle 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 intake: hygiene-blocked Deterministic PR hygiene checks failed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant