Skip to content

feat(native): add menu-bar popover and background attention - #682

Open
Farhan (fkb032) wants to merge 4 commits into
fix/helper-recoveryfrom
feat/menu-bar-background-work
Open

feat(native): add menu-bar popover and background attention#682
Farhan (fkb032) wants to merge 4 commits into
fix/helper-recoveryfrom
feat/menu-bar-background-work

Conversation

@fkb032

@fkb032 Farhan (fkb032) commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on helper recovery #681. The base is fix/helper-recovery so this diff contains only menu-bar/background-attention changes. Retarget to main after the helper PR merges.

Adds an opt-in menu-bar popover so queries and builds can continue while nixmac is hidden, with retained state and actionable native attention for questions, completion, failures and configuration drift.

The change handles tray/Dock/notification reopening, display positioning, and close/Escape routing through dismissible dialogs and Settings. Active work and unsaved editor contents stay mounted. Explicit dismissal returns keyboard focus to another app; normal-window behavior stays within the existing rollout boundary.

Rollout

Off by default. Enable Developer settings by clicking the version seven times under Settings > General, then choose Developer > Feature flags > Menu bar popover > Popover and fully quit/reopen nixmac.

The flag is a persisted local launch-time override, menu-bar-popover=popover. PostHog does not control it, diagnostics may remain off, and hiding Developer settings does not reset it. Choose Default (control) and restart to return to the normal window.

Review fixes

Close fallback ownership now survives failed, false and unanswered dismissal calls. Only the current native close token can hide the popover, and successful hide retires that token. Overlay-first handling and retained editor state stay intact.

Transient launch-mode lookup failures retry without poisoning the cache. Full History restores now notify after finalization succeeds or fails. Failure copy names the failed request or build/restore operation.

Test Plan

  • Latest patch: 1,412 Rust tests passed (14 ignored), 454 frontend tests passed; strict workspace Clippy with codegen, TypeScript and frontend production build passed. Generated oRPC bindings are idempotent.
  • Native close suite on cbc6b6146: Escape/Cmd+W/Close focus handoff and retained prompt; History retention and overlay-first Close; rejected/false/unanswered dismissal fallback; delayed close invalidated by reopen; an 8-second unresponsive WebView and recovery. Tray show/hide also passes from a clean launch.
  • Delayed local-provider success and failure both finish while hidden and retain their result.
  • Full History restore through the UI: mocked activation followed by real Git/SQLite finalization. Success creates the expected restored tree, commit and origin record. An index-lock failure creates no restore commit and retains the error. Both delivered outcome notifications reopen the retained view.
  • Startup retry in a separate instrumented native build: the first mode IPC lookup rejects, the next real backend lookup succeeds 250ms later, and ordinary Escape/Cmd+W work without a native-event override. The application entry module and Rust source are unchanged.
  • Normal-window compatibility: idle Escape preserves the window; Cmd+W closes History first, then hides the window; reopening and resizing work; the popover close API stays inactive.
  • Native 800 x 600 content: a 401-line diff, a 120-section reply and 500 lines of injected build-console fixture data remain scrollable with their actions reachable. Diff/reply scroll positions survive hide/reopen.
  • Positioning on both existing 3008 x 1692 displays. Temporarily removing the test tray item exercises the unavailable-item fallback; reopening remains visible and usable. The tray is restored afterward.
  • Two DeveloperTab DOM snapshots pass. The Evolve baseline drift is inherited from the base: the five-image gallery represents 24 failures also present on fix(native): clarify unattended sync helper recovery #681. No blanket baseline acceptance was applied; full Storybook success is not claimed.
  • All 12 GitHub checks pass on cbc6b6146.
  • Retarget to main after fix(native): clarify unattended sync helper recovery #681 merges, then verify the resulting diff and checks.

Native scope: isolated debug/e2e app with local fixtures and mocked system activation. Restore bookkeeping is real. The one-failure startup test uses a separate frontend-copy bootstrap; the original cbc6b6146 bundle was preserved and restored. Release signing, real system activation, display disconnection and additional screen sizes are outside this pass.

Standalone Filesystem and the editable NixEditor have no reachable entry point in this build. Their state-retention coverage remains automated; no native presentation claim is made for those surfaces.

Docs

  • No docs update needed for the local developer opt-in; the control explains its restart requirement.

#no-linear

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️ ❗ Big PR (5032 lines changed). Consider splitting it into smaller, focused changes.
⚠️

New Rust modules were added without tests. Consider adding a #[cfg(test)] mod tests { ... } block or a file under apps/native/src-tauri/tests/:

  • apps/native/src-tauri/src/orpc/main_window.rs
Messages
📖 No docs update needed — acknowledged.

📋 PR Overview

Lines changed 5032 (+4865 / -167)
Files 9 added, 29 modified, 0 deleted
Draft / WIP no
Has Test Plan yes
Linear issue no
No Test Plan Needed no
New UI components no
New Storybook stories no
New Rust modules yes (3)
New TS source files yes (3)
New tests yes (3)
package.json touched no
Cargo.toml touched yes
Infra / CI touched no

🔬 Coverage

Report Lines Statements Functions Branches
apps/native/coverage/coverage-summary.json 40.1% 39.6% 35.0% 34.0%

Generated by 🚫 dangerJS against cbc6b61

@darkmatter

darkmatter Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

🎨 Storybook preview

Open Storybook preview

Updated for cbc6b61


🧭 Story changes

Compared to fix/helper-recovery (snapshot diff at story level):

✏️ Changed stories (2)

💡 Update snapshots ↗ to regenerate baselines and open a PR.


⚠️ Detected UI changes (5)

These stories' HTML snapshots changed. I've added screenshots + links to the changed stories below. Review them carefully then accept the changes to regenerate baselines and include them in this PR:

Flows/Evolve › Playground

Flows/Evolve › Playground

Flows/Evolve › 1. Begin (idle)

Flows/Evolve › 1. Begin (idle)

Flows/Evolve › 2. Evolving (progress)

Flows/Evolve › 2. Evolving (progress)

Flows/Evolve › Evolving With Error Event

Flows/Evolve › Evolving With Error Event

Flows/Evolve › 3. Review (changes generated)

Flows/Evolve › 3. Review (changes generated)


Accept UI changes

  • Click here to accept these changes

Alternatively, you can run bun run test:update-snapshots locally to re-generate the baselines and then push the changes to this PR.

What does this do?

The screenshots above show UI changes detected by the Storybook
snapshot tests run on this PR. Each image is the rendered output of
a Storybook story from the code in this PR branch; the snapshot
test compared it against the committed baseline in
__snapshots__/ and flagged the difference.

Checking the box tells the darkmatter[bot] to regenerate the
baselines from this PR's current code and commit them directly to
this branch. The new baselines become the source of truth for
future runs — only accept after confirming the visual changes are
intentional.

Comparison baseline: the committed __snapshots__/ files on this
PR branch (carried forward from develop). Accept updates them in
place on this branch.

Compile Cocoa notification bookkeeping and geometry helpers only on macOS or in unit tests. This keeps Linux cargo check clean while preserving cross-platform unit coverage.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 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-11T01:30:37.543924Z cbc6b61 New commits
ℹ️ About Codex in GitHub

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

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

@prelint

prelint Bot commented Sep 7, 2026

Copy link
Copy Markdown

Ship with changes Menu-bar popover attention system and dismissal routing

Product decisions in this change

Agree 1. Dismissing the popover does not cancel active AI work or rebuilds; the operation continues in the background and notifies the user when it finishes.

This is the core value of popover mode. A user who hides the window while evolve is running expects the run to continue. The tests confirm that pressing Escape or Cmd+W during an active evolve, apply, or rebuild dismisses the window without touching the running state. The implementation is consistent: every in-progress state (generating, processing, rebuilding) gets the same treatment. The user regains keyboard focus in another app while work proceeds.

Agree 2. Notifications fire only when the popover window is not visible or not focused, except for the developer test command which fires regardless.

A user who is already watching the window does not need a system notification. The visibility and focus checks work together correctly: a window that is visible but not focused still triggers a notice, which is right because the user has switched apps. The developer test override is a reasonable exception for verifying the notification pipeline without having to hide the window first.

Agree 3. An active AI session suppresses drift notifications when the git state matches the session's recorded changes.

Without this guard, every AI evolution would produce two notifications: one for completion and one for "external changes detected" from the background watcher. The session ownership check uses the same content-addressed snapshot that the summarization pipeline uses, so it shares the product's existing concept of change identity. The test confirms that manual edits made after the AI run correctly break ownership, so subsequent manual changes still trigger drift notices. This is the right behavior and the mechanism is internally consistent.

Agree 4. Escape events route through a named priority stack where celebration overlays outrank widget overlays, which outrank popover dismissal.

The priority order matches user expectation: a full-screen celebration moment should not be dismissed by a background popover Escape. Radix dialogs get first refusal through defaultPrevented before the priority stack runs, preserving their existing behavior. The explicit priority system removes a fragile dependency on React effect registration order, which is the right decision for a feature that will grow new overlay types over time.

Agree with concerns 5. Native window close requests use a token-based protocol that routes through the DOM escape hierarchy before deciding whether to dismiss or acknowledge the close.

The token protocol solves a real problem: a Radix dialog that refuses to close should keep the popover visible, but a Radix dialog that does close on Escape should do so before the popover hides. Routing native close through DOM Escape achieves both. The concern is cognitive complexity: the distinction between dismissClose (proceed with hide), acknowledgeClose (a higher-priority owner handled it so the popover stays), and dismissPopover (token-free hide) is subtle. Any future developer who adds a new modal must understand this three-way split. The test suite covers the cases thoroughly, which reduces risk, but the protocol has more moving parts than standard close-window handling. Clear documentation is needed before popover mode exits the developer gate.

Agree 6. Finalize-apply failures now surface to the user instead of being silently logged.

The previous behavior swallowed finalize-apply errors in the apply path. A user whose build succeeded but whose commit step failed would see no feedback. The change propagates errors to the rebuild coordinator for the apply path and shows them in the error state for the manual build path. Both are correct. The test covers both cases and confirms the error message reaches the visible UI state.

Agree 7. Notification body text contains no AI output or configuration detail; all 14 notification types use fixed, generic copy.

A configuration manager handles system state, package names, dotfile paths, and sometimes secrets. Keeping that content out of notification text is a sound privacy decision. The copy is also actionable: every body directs the user to open nixmac for the next step. Lower specificity ("Open nixmac to continue." does not name what it needs) is the correct trade-off in this domain.

Agree with concerns 8. All 14 notification types open the popover at its current screen on click, without routing to the relevant context.

This concern carries forward from the prior review unchanged. The did_activate handler calls main_window::show() for every owned notification identifier with no per-type routing. A user who clicks "Your changes are ready to review" lands on whatever screen was last visible rather than the diff. For a developer alpha this is acceptable, but it is a friction point at scale.

Option What it gives users What it costs Effort to change later
Current (open at current screen) Simple delivery User must navigate after every click Moderate: the attention ID encodes the type already
Route to relevant screen User lands in context immediately Requires knowing app state at click time Moderate: map each of the 14 types to a target route

The taxonomy already encodes the distinct user states, so adding routing is a well-scoped follow-on task. It should be a required milestone before general promotion.

Agree with concerns 9. The popover window is fixed at 800 × 600 pixels and cannot be resized.

This concern carries forward from the prior review unresolved. The control window supports up to 1800px height, which indicates the UI regularly uses that vertical space. Long AI responses, multi-step build logs, and detailed diff sections may overflow the 600px container. Fixed size is aesthetically correct for a menu-bar popover, but 600px has not been validated against the full content range. At minimum, one multi-step evolution with a non-trivial diff should be run in popover mode before promotion. The unchecked test item in the PR description ("Targeted checks for multi-display/overflow positioning") confirms this is still open.

Agree with concerns 10. NSUserNotificationCenter (a deprecated macOS API) is retained as the notification backend for popover mode instead of the permission-gated UNUserNotificationCenter.

This concern carries forward from the prior review unchanged. The deprecated API avoids the permission prompt that UNUserNotificationCenter requires, which is a real UX benefit for a developer tool. The #[allow(deprecated)] markers make the choice visible in code review. The risk is that Apple removes the API in a future macOS release without a build-time warning, which would silently stop all popover notifications. There is still no documented migration plan. The decision is defensible for alpha, but a migration path should be written down before general rollout.

Agree with concerns 11. The floating preview indicator and spinning mascot are disabled in popover mode; system notifications are the only attention signal when the window is hidden.

This concern carries forward from the prior review unchanged. The preview indicator provides a persistent, ambient signal visible across all Spaces. A system notification is point-in-time and dismissible. Once a completion notice is dismissed, there is no persistent reminder that work finished while the user was away. No tray icon badge or menu-bar status mark is added in this PR. For a developer alpha with a small audience this is acceptable, but for general use a persistent indicator would fill the gap. This needs a plan before the flag graduates.

Agree 12. Tray left-click toggles the popover open and closed in popover mode; in control mode the left click shows the tray menu as before.

This matches standard macOS menu-bar app conventions. Apps like Bartender, Lungo, and Amphetamine all use tray left-click to toggle their popover. The tray menu still reaches every action (Feedback, Settings, Quit) via right-click or secondary-click in both modes, so no action becomes unreachable. Removing "Open nixmac" from the tray menu in popover mode is correct because the left click already performs that function.

Since the last review

  • Still open: Preview indicator suppressed in popover mode with no persistent replacement signal (peek.rs gates the indicator behind shows_detached_indicators() and main.rs skips window creation entirely in popover mode; no tray badge or persistent marker is added)
  • Still open: All notification clicks open the popover at its current screen without routing to the relevant context (attention.rs did_activate calls main_window::show() for every owned notification identifier with no per-type screen routing)
  • Still open: Popover window fixed at 600px height, unvalidated against multi-step evolve output and long diffs (main.rs still sets max_height = 600.0 and initial_height = 600.0 in popover mode; the PR description has the overflow/positioning test item unchecked)
  • Still open: NSUserNotificationCenter is deprecated and may be removed without a build-time warning (attention.rs macos module uses #[allow(deprecated)] throughout with no migration plan documented in code or PR description)
  • Still open: No persistent cross-Space signal for users who dismissed the completion notification (No tray badge, menu-bar status item, or persistent indicator added; system notifications remain the sole signal when the window is hidden)
  • Still open: Whether opening the popover at the current screen is the intended long-term destination for all notification types (attention.rs did_activate maps all 14 notification types uniformly to main_window::show() with no per-type routing and no design comment about future intent)
  • Still open: Whether the 600px height has been validated against multi-step evolve output and long configuration diffs (PR description leaves the targeted overflow/multi-display check unchecked; height constraint is unchanged)
  • Still open: Plans for per-type notification preferences to suppress low-signal notices while keeping high-priority ones (No preference system for notification types added; all 14 types fire uniformly when visibility and focus conditions are met)
  • Still open: Documented migration plan from NSUserNotificationCenter at a defined macOS release boundary (No migration plan in code or PR description; #[allow(deprecated)] is used as acknowledgment only with no target macOS version or timeline)

Open questions

  • Has the 600px fixed height been tested against a multi-step evolution with a non-trivial configuration diff? This is the most concrete usability risk before promotion to general availability.

  • Is routing notification clicks to the relevant screen (diff view for ChangesReady, blocker dialog for BuildBlocked, question panel for InputRequired) a planned milestone before the flag exits the developer gate?

  • The BuildSucceeded notification fires after both AI-driven and user-initiated builds when the window is dismissed. Is this the intended behavior, or should a build the user explicitly started in a session they were already watching skip the completion notice?

Recommendation

Ship with changes
The new decisions in this iteration are sound: the attention taxonomy, session-ownership drift suppression, escape priority routing, token-based close protocol, and finalize-apply error surfacing are all well-designed and well-tested. The feature remains behind a developer-only flag, so real users are not yet affected by any gap. The changes that need resolution before promotion are the unvalidated 600px fixed height, the missing notification-click context routing, and the absent persistent ambient signal after notification dismissal. None of these block shipping the developer alpha, but all three need concrete plans before the flag graduates to general availability.

@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: 2354ad63c5

ℹ️ 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 (@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 (@codex) address that feedback".

Comment thread apps/native/src-tauri/src/main_window.rs Outdated
Comment thread apps/native/src/components/widget/widget.tsx Outdated

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

ℹ️ 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 (@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 (@codex) address that feedback".

Comment thread apps/native/src/lib/main-window.ts Outdated
Comment thread apps/native/src-tauri/src/rebuild/darwin.rs
@fkb032

Farhan (fkb032) commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, I checked each open question against the branch:

  • History / Filesystem: Clicking away hides the window without resetting the view. Native checks on cbc6b61 confirm History returns on reopen, while native Close dismisses History first and leaves the popover open. Standalone Filesystem and the editable NixEditor have no reachable UI entry point in this build, so their retention coverage remains automated.
  • Pending changes across Spaces: There is no persistent replacement for the floating preview indicator in this opt-in. A persistent menu-bar pending-change indicator remains follow-up scope for wider rollout.
  • Notification clicks: They reopen the retained view. Both full History restore success and finalization-error notices were delivered and activated successfully. Routing to a particular diff or blocker remains a separate follow-up, including how old notifications interact with newer work and unsaved edits.
  • 600px height: Native checks at 800 x 600 pass for a 401-line diff, a 120-section reply and 500 lines of build-console fixture data. Content scrolls and the primary controls remain reachable. Diff/reply scroll positions survive hide/reopen. Both existing displays and a controlled unavailable-tray fallback were checked; other screen sizes and display disconnection were not part of this pass.
  • Notification preferences: There is no per-category selector here. An action-needed-only option for wider rollout would retain questions, blockers and failures while silencing routine completion notices.

Failure copy is updated: evolution failure says “Your request stopped with an error”; the shared build/restore path says “Build or restore failed” with recovery guidance. Finalization errors retain the distinction that changes may already be active. The full History restore path now also notifies on finalization success or failure.

The feature stays a local developer opt-in with an explicit restart requirement. A dedicated preference and migration of the local override belong with wider rollout. Normal-window shortcuts and resizing passed their native checks; dismissing the popover keeps active work running.

The latest patch passes 1,412 Rust tests (14 ignored), 454 frontend tests, TypeScript, the frontend build, strict workspace Clippy and all 12 GitHub checks. Native checks use an isolated debug/e2e app with mocked system activation; restore bookkeeping is real. The forced first-lookup failure uses a separate instrumented startup fixture. The PR checklist records the exact scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant