feat: ship v0.1.0 production readiness (prebuilt brew + privacy) - #31
Conversation
Switch Homebrew to prebuilt release assets, fix formula bump checksums, enforce socket 0600, add PRIVACY.md, bump to 0.1.0, and harden install docs/UI honesty ahead of public launch. HID remains post-Jul-22.
📝 WalkthroughWalkthroughThe PR prepares the v0.1.0 release, changes Homebrew to install prebuilt macOS artifacts, updates UI demo and device-state handling, tightens daemon socket permissions, and expands privacy, security, installation, and project documentation. ChangesRelease distribution and versioning
UI device-state presentation
Local control, security, and project policy
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/microbridged/src/frontmost.rs`:
- Around line 4-7: Update Registry::resolve_focus so it evaluates frontmost_app
and switches to the new application’s session when focus changes, rather than
returning early solely because the current focus still exists; ensure
set_frontmost_app triggers this behavior and add a regression test covering live
application switching. If live switching cannot be implemented, revise the
frontmost.rs documentation to state that auto-follow is currently limited.
In `@crates/microbridged/src/socket.rs`:
- Around line 19-24: Update the parent-directory permission setup in the socket
initialization flow to propagate errors from std::fs::set_permissions instead of
ignoring them. Ensure failure to apply mode 0700 returns before remove_file or
UnixListener::bind proceeds, while preserving the existing socket mode handling.
In `@docs/architecture.md`:
- Around line 44-46: Update the Auto-follow description in the architecture
documentation to accurately state that the watcher polls NSWorkspace every 400ms
to identify the frontmost app. Remove the claim that it is event-driven and not
polled, and note that NSWorkspace notifications are planned future work.
In `@Formula/microbridge.rb`:
- Around line 52-55: Update post_install to write the .microbridge-brew
ownership marker beside the installed Microbridge.app rather than inside its
signed bundle. Keep the resource("ui").stage installation flow and app path
unchanged, and ensure any marker path references use the external location
consistently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f4334359-d06b-45e4-9163-2f557a63a5b2
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockapps/microbridge-ui/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (23)
.github/ISSUE_TEMPLATE/bug_report.ymlCONTRIBUTING.mdCargo.tomlFormula/microbridge.rbINSTALL.mdPRIVACY.mdREADME.mdSECURITY.mdapps/microbridge-ui/package.jsonapps/microbridge-ui/src-tauri/Cargo.tomlapps/microbridge-ui/src-tauri/tauri.conf.jsonapps/microbridge-ui/src/lib/bus.tsapps/microbridge-ui/src/surfaces/Popover.tsxapps/microbridge-ui/src/surfaces/Settings.tsxcrates/microbridged/src/frontmost.rscrates/microbridged/src/registry.rscrates/microbridged/src/socket.rscrates/microbridged/src/state.rsdocs/architecture.mddocs/governance.mddocs/project-tracking.mdscripts/bump-formula.shscripts/install-from-release.sh
| const demo = snapshot.device_name === "demo-browser"; | ||
| const simulator = snapshot.device_name === "mock" || demo; | ||
| const detected = | ||
| !snapshot.device_connected && snapshot.device_name.includes("usb"); | ||
| const connected = snapshot.device_connected || simulator || detected; | ||
| // Show the live UI shell in simulator/detected modes; only "Connected" | ||
| // means claimed HID (not yet shipped for production hardware). | ||
| const showLiveShell = | ||
| snapshot.device_connected || simulator || detected; | ||
| const chipLabel = snapshot.device_connected | ||
| ? "Connected" | ||
| : detected | ||
| ? "Detected" | ||
| : simulator | ||
| ? "Simulator" | ||
| : "Disconnected"; | ||
| : demo | ||
| ? "Demo" | ||
| : simulator | ||
| ? "Simulator" | ||
| : "Disconnected"; | ||
| const chipTone = snapshot.device_connected | ||
| ? "ok" | ||
| : detected || simulator | ||
| ? "warn" | ||
| : "off"; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep demo-browser consistent across the UI.
bus.ts:31-32 now emits device_name: "demo-browser", so Popover labels the state as Demo; however, Settings.tsx:374-381 only recognizes "mock" and otherwise displays not connected. The Device tab will therefore disagree with this status. Reuse isDemoSnapshot(snapshot) or add the same demo branch there.
| //! pushes changes onto the bus. This is an intentional, documented exception to | ||
| //! the “no timers” footprint ideal — a CFRunLoop notification observer can | ||
| //! replace the poll later. The daemon (not the UI) owns this signal so | ||
| //! `--no-ui` still auto-follows. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Do not document auto-follow as working until focus resolution can switch live focus.
The watcher publishes frontmost changes, but Registry::resolve_focus returns early when the current focus still exists, before evaluating frontmost_app. Since set_frontmost_app calls resolve_focus, changing applications does not move focus to the new app’s session. Either fix that policy and add a regression test, or document the current limitation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/microbridged/src/frontmost.rs` around lines 4 - 7, Update
Registry::resolve_focus so it evaluates frontmost_app and switches to the new
application’s session when focus changes, rather than returning early solely
because the current focus still exists; ensure set_frontmost_app triggers this
behavior and add a regression test covering live application switching. If live
switching cannot be implemented, revise the frontmost.rs documentation to state
that auto-follow is currently limited.
| // Keep the config dir private; the socket itself is locked to 0600 below. | ||
| let _ = std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700)); | ||
| } | ||
| let _ = std::fs::remove_file(&path); | ||
| let listener = UnixListener::bind(&path)?; | ||
| std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Fail closed when private directory permissions cannot be applied.
Line 20 ignores set_permissions errors, then the daemon proceeds to bind. If an existing directory cannot be chmodded to 0700, other local users may still traverse it and connect using the socket’s effective mode, contradicting the documented privacy guarantee. Propagate the error before binding.
Proposed fix
- // Keep the config dir private; the socket itself is locked to 0600 below.
- let _ = std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700));
+ // Fail closed if the config directory cannot be made private.
+ std::fs::set_permissions(
+ parent,
+ std::fs::Permissions::from_mode(0o700),
+ )?;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Keep the config dir private; the socket itself is locked to 0600 below. | |
| let _ = std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700)); | |
| } | |
| let _ = std::fs::remove_file(&path); | |
| let listener = UnixListener::bind(&path)?; | |
| std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?; | |
| // Fail closed if the config directory cannot be made private. | |
| std::fs::set_permissions( | |
| parent, | |
| std::fs::Permissions::from_mode(0o700), | |
| )?; | |
| } | |
| let _ = std::fs::remove_file(&path); | |
| let listener = UnixListener::bind(&path)?; | |
| std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/microbridged/src/socket.rs` around lines 19 - 24, Update the
parent-directory permission setup in the socket initialization flow to propagate
errors from std::fs::set_permissions instead of ignoring them. Ensure failure to
apply mode 0700 returns before remove_file or UnixListener::bind proceeds, while
preserving the existing socket mode handling.
| 3. **Auto-follow.** Otherwise the frontmost app's active session owns the | ||
| deck — driven by `NSWorkspace` frontmost-app notifications (event-driven, | ||
| not polled). |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Document the current polling implementation.
This section contradicts line 24 and frontmost.rs, where the watcher polls NSWorkspace every 400ms. Replace “event-driven, not polled” with the current polling behavior and mention notifications as future work.
Proposed wording
-3. **Auto-follow.** Otherwise the frontmost app's active session owns the
- deck — driven by `NSWorkspace` frontmost-app notifications (event-driven,
- not polled).
+3. **Auto-follow.** Otherwise the frontmost app's active session owns the
+ deck — currently tracked by a 400ms `NSWorkspace` poll; a notification
+ observer can replace this later.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 3. **Auto-follow.** Otherwise the frontmost app's active session owns the | |
| deck — driven by `NSWorkspace` frontmost-app notifications (event-driven, | |
| not polled). | |
| 3. **Auto-follow.** Otherwise the frontmost app's active session owns the | |
| deck — currently tracked by a 400ms `NSWorkspace` poll; a notification | |
| observer can replace this later. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/architecture.md` around lines 44 - 46, Update the Auto-follow
description in the architecture documentation to accurately state that the
watcher polls NSWorkspace every 400ms to identify the frontmost app. Remove the
claim that it is event-driven and not polled, and note that NSWorkspace
notifications are planned future work.
| resource("ui").stage do | ||
| app = Dir["**/Microbridge.app"].first | ||
| odie "Microbridge.app missing from UI release archive" if app.nil? | ||
| prefix.install app |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Keep the Homebrew ownership marker outside the signed app bundle.
Line 55 installs the prebuilt signed app, but post_install subsequently writes .microbridge-brew inside that bundle. This changes its sealed contents and can invalidate strict signature verification. Store the marker beside the app instead.
Proposed fix
apps = Pathname.new(Dir.home)/"Applications"
apps.mkpath
dest = apps/"Microbridge.app"
- marker = dest/".microbridge-brew"
+ marker = apps/".Microbridge.app.microbridge-brew"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Formula/microbridge.rb` around lines 52 - 55, Update post_install to write
the .microbridge-brew ownership marker beside the installed Microbridge.app
rather than inside its signed bundle. Keep the resource("ui").stage installation
flow and app path unchanged, and ensure any marker path references use the
external location consistently.
There was a problem hiding this comment.
Pull request overview
This PR prepares Microbridge for the v0.1.0 “public alpha” release by aligning install/distribution paths with GitHub Release assets, tightening local-only security/privacy posture, and updating UI/docs to be explicit about current hardware/HID limitations.
Changes:
- Switch Homebrew + formula bump tooling to use prebuilt GitHub Release assets (daemon + UI) and align install docs accordingly.
- Add OSS hardening documentation (
PRIVACY.md, updatedSECURITY.md) and tighten daemon socket/dir permissions. - UI/UX “honesty pass” (Simulator/Detected/Demo/Connected semantics) + version bumps across Rust/Tauri/UI packages.
Reviewed changes
Copilot reviewed 23 out of 25 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| SECURITY.md | Expands security policy scope/reporting details and clarifies local socket posture. |
| scripts/install-from-release.sh | Gates Linux aarch64 release installs until binaries are published. |
| scripts/bump-formula.sh | Reworks formula bump script to hash the same prebuilt release asset URLs Homebrew will fetch. |
| README.md | Updates public status messaging and sets expectations about HID timing. |
| PRIVACY.md | Adds a privacy statement emphasizing local-only operation and no telemetry/networking. |
| INSTALL.md | Updates release install examples and documents Homebrew’s prebuilt install behavior. |
| Formula/microbridge.rb | Changes formula to install prebuilt daemon + UI release archives (instead of building from source). |
| docs/project-tracking.md | Documents milestone-based tracking and Linear bridging plan. |
| docs/governance.md | Updates supported install path language to reflect prebuilt Homebrew installs. |
| docs/architecture.md | Documents footprint budget exception for frontmost polling and updates focus model wording. |
| crates/microbridged/src/state.rs | Clarifies action forwarding behavior for in-process adapters pending #24. |
| crates/microbridged/src/socket.rs | Tightens socket directory/socket file permissions. |
| crates/microbridged/src/registry.rs | Updates focus resolution doc comment regarding auto-follow behavior. |
| crates/microbridged/src/frontmost.rs | Clarifies polling watcher as an intentional “no timers” exception. |
| CONTRIBUTING.md | Updates tagging/install-from-release examples to v0.1.0. |
| Cargo.toml | Bumps workspace version to 0.1.0. |
| Cargo.lock | Updates crate versions to 0.1.0. |
| apps/microbridge-ui/src/surfaces/Settings.tsx | Marks Cursor/T3 adapters as scaffold-only / not production. |
| apps/microbridge-ui/src/surfaces/Popover.tsx | Refines device state chip semantics and adds clearer messaging for demo/simulator/detected. |
| apps/microbridge-ui/src/lib/bus.ts | Adjusts browser demo snapshot semantics and simplifies demo detection helper. |
| apps/microbridge-ui/src-tauri/tauri.conf.json | Bumps Tauri app version to 0.1.0. |
| apps/microbridge-ui/src-tauri/Cargo.toml | Bumps UI crate version to 0.1.0. |
| apps/microbridge-ui/package.json | Bumps UI package version to 0.1.0. |
| apps/microbridge-ui/package-lock.json | Updates lockfile package versions to 0.1.0. |
| .github/ISSUE_TEMPLATE/bug_report.yml | Updates bug template placeholder version to v0.1.0. |
Files not reviewed (1)
- apps/microbridge-ui/package-lock.json: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| on_arm do | ||
| url "https://github.com/DevVig/microbridge/releases/download/v#{version}/microbridge-v#{version}-aarch64-apple-darwin.tar.gz" | ||
| # sha256 filled by scripts/bump-formula.sh after each release | ||
| sha256 "0000000000000000000000000000000000000000000000000000000000000000" |
|
|
||
| resource "ui" do | ||
| url "https://github.com/DevVig/microbridge/releases/download/v#{version}/microbridge-ui-v#{version}-aarch64-apple-darwin.tar.gz" | ||
| sha256 "0000000000000000000000000000000000000000000000000000000000000000" |
| end | ||
| on_intel do | ||
| url "https://github.com/DevVig/microbridge/releases/download/v#{version}/microbridge-v#{version}-x86_64-apple-darwin.tar.gz" | ||
| sha256 "0000000000000000000000000000000000000000000000000000000000000000" |
| system "npm", "run", "tauri", "build", "--", "--bundles", "app" | ||
| resource "ui" do | ||
| url "https://github.com/DevVig/microbridge/releases/download/v#{version}/microbridge-ui-v#{version}-x86_64-apple-darwin.tar.gz" | ||
| sha256 "0000000000000000000000000000000000000000000000000000000000000000" |
| def install | ||
| # Release tarball layout: microbridge-vX.Y.Z-<target>/{microbridged,microbridgectl,…} | ||
| bin.install Dir["**/microbridged"].first | ||
| bin.install Dir["**/microbridgectl"].first |
| std::fs::create_dir_all(parent)?; | ||
| // Keep the config dir private; the socket itself is locked to 0600 below. | ||
| let _ = std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700)); | ||
| } |
| 3. **Auto-follow.** Otherwise the frontmost app's active session owns the | ||
| deck — driven by `NSWorkspace` frontmost-app notifications (event-driven, | ||
| not polled). |
| Microbridge runs unprivileged on your Mac, listens only on a **local Unix | ||
| socket** (mode `0600` under `~/.microbridge/`), and performs **no network I/O**. |
Summary
bump-formula.shhashes the same URLs brew fetchesPRIVACY.md, tighterSECURITY.md, Unix socket/dir0600/0700docs/project-tracking.mdCloses #19, #21, #22, #23, #25, #26 (partially #20/#24 remain for tag smoke + action bridge).
Test plan
v0.1.0and babysit Release (signed DMGs)brew tap/brew installsmokeMade with Cursor
Summary by CodeRabbit
New Features
Documentation
Bug Fixes