Conversation
Adds AppPane type that launches external macOS apps (e.g. Figma) and repositions their windows into Tide's editor panel area using the Accessibility API (AXUIElement) for positioning/resizing and CGS private APIs for window level manipulation. Known limitations: - Z-ordering relies on lowering Tide's window level via CGS, which is fragile across focus changes - Apps with minimum window sizes (e.g. Figma 900pt) overflow the panel - Window discovery fails when the target app has no visible windows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request explores the feasibility of integrating external macOS application windows directly into Tide's UI as dock panel tabs. It introduces core functionality for launching, embedding, and managing these external app windows, providing a proof-of-concept for a more unified workspace experience. The changes encompass new UI elements, event handling, and platform-specific macOS API interactions, while also highlighting significant technical challenges and limitations inherent in this approach. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This is an impressive proof-of-concept for embedding external application windows. The use of Accessibility APIs and private CGS functions is well-encapsulated. My review focuses on a few areas that could be improved for maintainability and efficiency, particularly around code duplication and polling mechanisms. These suggestions are aimed at strengthening the design if this POC were to be developed into a full feature.
| if let Some(PaneKind::Browser(bp)) = self.panes.get_mut(&tab_id) { | ||
| bp.destroy(); | ||
| } | ||
| if let Some(PaneKind::App(ap)) = self.panes.get_mut(&tab_id) { | ||
| ap.destroy(); | ||
| } |
There was a problem hiding this comment.
This destruction logic for BrowserPane and AppPane is duplicated in a few places (here and in close_specific_pane). To improve maintainability, consider adding a destroy method to the PaneKind enum that handles this logic internally. This would centralize the cleanup logic for all pane types that require it.
For example, you could add this to pane.rs:
impl PaneKind {
pub fn destroy(&mut self) {
match self {
PaneKind::Browser(bp) => bp.destroy(),
PaneKind::App(ap) => ap.destroy(),
_ => {}
}
}
}Then the call sites would be simplified to:
if let Some(pane) = self.panes.get_mut(&tab_id) {
pane.destroy();
}| let app_ids: Vec<tide_core::PaneId> = self | ||
| .panes | ||
| .iter() | ||
| .filter_map(|(&id, pk)| { | ||
| if matches!(pk, PaneKind::App(_)) { | ||
| Some(id) | ||
| } else { | ||
| None | ||
| } | ||
| }) | ||
| .collect(); |
There was a problem hiding this comment.
Collecting app_ids into a Vec on each layout computation introduces a small but frequent allocation. You could iterate directly over the panes to avoid this, which would be more efficient.
For example:
for (id, pane) in self.panes.iter_mut() {
if let PaneKind::App(ap) = pane {
let is_active = active_app_id == Some(*id);
// ... existing logic from inside your loop ...
}
}| let app_ids: Vec<tide_core::PaneId> = self | ||
| .panes | ||
| .iter() | ||
| .filter_map(|(&id, pk)| { | ||
| if matches!(pk, PaneKind::App(_)) { | ||
| Some(id) | ||
| } else { | ||
| None | ||
| } | ||
| }) | ||
| .collect(); |
There was a problem hiding this comment.
Collecting app_ids into a Vec on each update cycle introduces a small but frequent allocation. You can iterate directly over self.panes.values_mut() to make this more efficient.
For example:
let mut needs_layout = false;
for pane in self.panes.values_mut() {
if let PaneKind::App(ap) = pane {
// ... existing logic from inside your loop ...
// You will need to get the pane id if you need it.
}
}
if needs_layout {
self.sync_app_pane_frames();
}| // Poll for app to start (up to 2 seconds) | ||
| for attempt in 0..20 { | ||
| std::thread::sleep(std::time::Duration::from_millis(100)); | ||
| let running_apps2: Retained<AnyObject> = msg_send_id![ | ||
| objc2::runtime::AnyClass::get("NSRunningApplication").unwrap(), | ||
| runningApplicationsWithBundleIdentifier: &*ns_bundle_id | ||
| ]; | ||
| let count2: usize = msg_send![&*running_apps2, count]; | ||
| if count2 > 0 { | ||
| let app: Retained<AnyObject> = msg_send_id![&*running_apps2, objectAtIndex: 0usize]; | ||
| let pid: i32 = msg_send![&*app, processIdentifier]; | ||
| if pid > 0 { | ||
| log::info!("launch_or_find_app: started after {}ms, pid={}", (attempt + 1) * 100, pid); | ||
| return Some(pid as u32); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
This polling loop to wait for the app to start is functional for a proof-of-concept, but it's not very efficient and has a fixed timeout. A more robust and idiomatic macOS approach would be to use notifications.
Consider using NSWorkspace notifications, specifically NSWorkspaceDidLaunchApplicationNotification. You can add an observer for this notification to be called back when the application has finished launching, which would eliminate the need for sleeping and polling.
- Add Pencil (dev.pencil.desktop) as an embeddable app option - Call sync_app_pane_frames() every frame so embedded windows hide/show correctly when switching dock tabs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
AppPanetype that embeds external macOS app windows (e.g. Figma) inside Tide's editor panel tabsCmd+Shift+P) now includes app launch optionsKnown Limitations
SetWindowLevelhas no effect on cross-process windows on modern macOSStatus
POC / Do not merge — exploring feasibility of native window embedding via macOS private APIs. The approach has fundamental limitations around z-ordering and cross-process window control.
Test plan
Cmd+Shift+P→ select Figma → app launches and window repositions into panelCmd+W) → embedded window returns to normal🤖 Generated with Claude Code