Skip to content

Commit aa5eafb

Browse files
feat(Mountain): Restore recently opened folder on launch, fix macOS window drag
Two improvements to the window boot sequence: 1. **Recently opened folder restoration (Atom W7):** Add BuildInitialUrl to read `~/.land/workspaces/RecentlyOpened.json` and pass `?folder=<path>` to the initial webview URL. This skips the destructive round-trip where every launch landed on Welcome, forced users to click "Open Folder", and triggered a hard reload that wiped workbench state. That reload was the direct cause of the purple splash flash, stuttering paint, empty `@builtin` sidebar, and broken keybindings (every layer boots twice, second pass loses first-pass references). 2. **Fix macOS window dragging:** Remove `maximized(true)` from the window builder. Maximized macOS windows are pinned to the screen and refuse all drag events regardless of `-webkit-app-region: drag` CSS. Replace with `TitleBarStyle::Overlay` + `hidden_title(true)` which keeps traffic lights visible while letting VS Code's custom titlebar paint the drag region. Windows/Linux continue using `decorations(false)` for chrome-less operation. Also fix en-dash to hyphen in three documentation comments (ExtensionDescriptionStateDTO, LandFixTier) to match project convention.
1 parent 4ff7003 commit aa5eafb

3 files changed

Lines changed: 122 additions & 18 deletions

File tree

Source/ApplicationState/DTO/ExtensionDescriptionStateDTO.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ pub struct ExtensionDescriptionStateDTO {
113113
/// "Keymaps", "SCM Providers", "Testing", "Education", "Other").
114114
///
115115
/// Atom TH1: Wind's Extensions sidebar filters `@builtin category:themes`
116-
/// against this array. Without the field the filter never matches
116+
/// against this array. Without the field the filter never matches -
117117
/// user reported theme extensions absent on @builtin search despite
118118
/// being on disk. Scanner-passthrough surfaces the raw package.json
119119
/// value; resolved NLS placeholders survive because the serde
@@ -133,7 +133,7 @@ pub struct ExtensionDescriptionStateDTO {
133133
#[serde(default, skip_serializing_if = "Option::is_none")]
134134
pub Description:Option<String>,
135135

136-
/// Extension keywords array searched by the sidebar when the query
136+
/// Extension keywords array - searched by the sidebar when the query
137137
/// doesn't match `name`, `displayName`, or `description`.
138138
#[serde(default, skip_serializing_if = "Option::is_none")]
139139
pub Keywords:Option<Vec<String>>,
@@ -158,7 +158,7 @@ pub struct ExtensionDescriptionStateDTO {
158158
#[serde(default, skip_serializing_if = "Option::is_none")]
159159
pub Icon:Option<String>,
160160

161-
/// Marketplace API key placeholder still present in some upstream
161+
/// Marketplace API key placeholder - still present in some upstream
162162
/// built-in manifests. `@vscode/extension-telemetry` reads its
163163
/// length on construction; if missing the activate throws
164164
/// `Cannot read properties of undefined (reading 'length')`.
@@ -171,15 +171,15 @@ pub struct ExtensionDescriptionStateDTO {
171171
#[serde(default, skip_serializing_if = "Option::is_none", rename = "extensionKind")]
172172
pub ExtensionKind:Option<Value>,
173173

174-
/// Capabilities descriptor `untrustedWorkspaces`, `virtualWorkspaces`.
174+
/// Capabilities descriptor - `untrustedWorkspaces`, `virtualWorkspaces`.
175175
#[serde(default, skip_serializing_if = "Option::is_none")]
176176
pub Capabilities:Option<Value>,
177177

178-
/// Dependency list other extensions this one needs activated first.
178+
/// Dependency list - other extensions this one needs activated first.
179179
#[serde(default, skip_serializing_if = "Option::is_none", rename = "extensionDependencies")]
180180
pub ExtensionDependencies:Option<Vec<String>>,
181181

182-
/// Extension-pack children extensions this one bundles by reference.
182+
/// Extension-pack children - extensions this one bundles by reference.
183183
#[serde(default, skip_serializing_if = "Option::is_none", rename = "extensionPack")]
184184
pub ExtensionPack:Option<Vec<String>>,
185185
}

Source/Binary/Build/WindowBuild.rs

Lines changed: 114 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
55
use tauri::{App, WebviewUrl, WebviewWindowBuilder, Wry};
66

7+
use crate::IPC::WindServiceHandlers::Utilities::ReadRecentlyOpened;
8+
79
/// Creates and configures the main application window.
810
///
911
/// # Arguments
@@ -17,28 +19,67 @@ use tauri::{App, WebviewUrl, WebviewWindowBuilder, Wry};
1719
///
1820
/// # Platform-Specific Behavior
1921
///
20-
/// - Windows/macOS/Linux: Sets title, maximized state, no decorations, and
21-
/// shadow effect
22-
/// - Debug builds: Automatically opens DevTools
22+
/// - **macOS**: `TitleBarStyle::Overlay` + `hidden_title(true)` keeps the
23+
/// traffic-light buttons at the top-left but hides the native title bar
24+
/// strip, so VS Code's custom titlebar (which has `-webkit-app-region:
25+
/// drag` baked into its CSS) lights up the entire top row as a drag
26+
/// region. The previous `maximized(true)` was the direct cause of "can't
27+
/// drag the editor around" - maximized macOS windows are pinned to the
28+
/// screen and refuse all drag events, regardless of `app-region` CSS.
29+
/// - **Windows / Linux**: `decorations(false)` keeps the window chrome-less
30+
/// so the workbench draws its own. We still start `resizable(true)` so
31+
/// the window can be moved by the drag region.
32+
/// - **Debug builds**: DevTools auto-open.
2333
pub fn WindowBuild(Application:&mut App, LocalhostUrl:String) -> tauri::WebviewWindow<Wry> {
24-
// Create the window URL pointing to the application
34+
// Restore the most-recently-opened folder so the webview boots
35+
// directly into the workspace. Without this, every launch lands
36+
// on the Welcome tab, the user clicks "Open Folder", and the
37+
// `pickFolderAndOpen` handler fires `Window.navigate()` - a hard
38+
// reload that wipes workbench state mid-initialisation and is
39+
// the direct cause of the purple splash flash, stuttering paint,
40+
// empty `@builtin` sidebar, and broken keybindings (every layer
41+
// has to boot twice and the second pass often loses references
42+
// to the first). Using `?folder=...` in the initial URL skips
43+
// that destructive round-trip.
44+
let InitialUrl = BuildInitialUrl(&LocalhostUrl);
2545
let WindowUrl = WebviewUrl::External(
26-
format!("{}/index.html", LocalhostUrl)
46+
InitialUrl
2747
.parse()
28-
.expect("FATAL: Failed to parse localhost URL"),
48+
.expect("FATAL: Failed to parse initial webview URL"),
2949
);
3050

3151
// Configure window builder with base settings
3252
let mut WindowBuilder = WebviewWindowBuilder::new(Application, "main", WindowUrl)
3353
.use_https_scheme(false)
3454
.initialization_script("")
3555
.zoom_hotkeys_enabled(true)
36-
.browser_extensions_enabled(false);
56+
.browser_extensions_enabled(false)
57+
.title("Mountain")
58+
.resizable(true)
59+
.inner_size(1400.0, 900.0)
60+
.shadow(true);
61+
62+
#[cfg(target_os = "macos")]
63+
{
64+
// Overlay style lets VS Code's custom titlebar paint behind the
65+
// traffic-light buttons. `hidden_title(true)` suppresses the OS
66+
// title text so it doesn't collide with the workbench menubar.
67+
// `decorations(true)` is REQUIRED for the traffic lights to
68+
// render - turning decorations off also removes the buttons and
69+
// breaks the native drag + resize handles entirely on macOS.
70+
WindowBuilder = WindowBuilder
71+
.title_bar_style(tauri::TitleBarStyle::Overlay)
72+
.hidden_title(true)
73+
.decorations(true);
74+
}
3775

38-
// Apply platform-specific window configurations
39-
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
76+
#[cfg(any(target_os = "windows", target_os = "linux"))]
4077
{
41-
WindowBuilder = WindowBuilder.title("Mountain").maximized(true).decorations(false).shadow(true);
78+
// Keep chrome-less on non-macOS - the workbench provides its
79+
// own window controls via the WindowsTitleBar / LinuxTitleBar
80+
// components. Drag works via `-webkit-app-region: drag` on the
81+
// titlebar element.
82+
WindowBuilder = WindowBuilder.decorations(false);
4283
}
4384

4485
// Build the main window
@@ -52,3 +93,66 @@ pub fn WindowBuild(Application:&mut App, LocalhostUrl:String) -> tauri::WebviewW
5293

5394
MainWindow
5495
}
96+
97+
/// Build the initial webview URL, optionally appending `?folder=<path>`
98+
/// when `~/.land/workspaces/RecentlyOpened.json` has an entry for the
99+
/// previous session's workspace. Falls back to plain `index.html` if
100+
/// the file is missing, malformed, or has no resolvable path.
101+
///
102+
/// The returned string is already URL-encoded and safe to feed to
103+
/// `WebviewUrl::External`.
104+
fn BuildInitialUrl(LocalhostUrl:&str) -> String {
105+
let Base = format!("{}/index.html", LocalhostUrl);
106+
107+
let Recent = match ReadRecentlyOpened() {
108+
Ok(Value) => Value,
109+
Err(_) => return Base,
110+
};
111+
let Workspaces = match Recent.get("workspaces").and_then(|V| V.as_array()) {
112+
Some(Array) if !Array.is_empty() => Array,
113+
_ => return Base,
114+
};
115+
116+
// VS Code's Recently-Opened record can store the folder under a few
117+
// different shapes depending on whether the entry came from the
118+
// extension host, the workbench, or a `$deltaWorkspaceFolders`
119+
// broadcast. Probe them in the same priority order the workbench
120+
// itself uses in `getRecentlyOpenedWorkspaces`.
121+
let Probe = |Entry:&serde_json::Value| -> Option<String> {
122+
if let Some(Uri) = Entry.get("folderUri").and_then(|V| V.as_str()) {
123+
return Some(Uri.to_string());
124+
}
125+
if let Some(Path) = Entry.get("folderUri").and_then(|V| V.get("path")).and_then(|V| V.as_str()) {
126+
return Some(Path.to_string());
127+
}
128+
if let Some(Path) =
129+
Entry.get("workspace").and_then(|V| V.get("configPath")).and_then(|V| V.get("path")).and_then(|V| V.as_str())
130+
{
131+
return Some(Path.to_string());
132+
}
133+
None
134+
};
135+
136+
let FolderPath = match Workspaces.iter().find_map(Probe) {
137+
Some(Path) => Path,
138+
None => return Base,
139+
};
140+
141+
// Strip any `file://` scheme so the query param is a plain path
142+
// the workbench will stringify into a `file:` URI itself; leaving
143+
// the scheme in doubles up and breaks the URL-decode on the other
144+
// side (observed as the second `?folder=` boot path appearing as
145+
// `file:/Volumes/...` in `wb:boot`).
146+
let Normalised = FolderPath
147+
.strip_prefix("file://")
148+
.unwrap_or(FolderPath.as_str())
149+
.to_string();
150+
if !std::path::Path::new(&Normalised).is_dir() {
151+
return Base;
152+
}
153+
154+
let Encoded = url::form_urlencoded::Serializer::new(String::new())
155+
.append_pair("folder", &Normalised)
156+
.finish();
157+
format!("{}/?{}", LocalhostUrl, Encoded)
158+
}

Source/LandFixTier.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! Emits a single ISO-timestamped boot banner listing the compiled-in value of
44
//! every tier variable. Because all `env!("Tier…")` calls are resolved by
55
//! `build.rs::PropagateTierGating` at compile time, the banner always reflects
6-
//! the exact configuration baked into *this* binary not whatever the host
6+
//! the exact configuration baked into *this* binary - not whatever the host
77
//! environment happens to export at runtime.
88
//!
99
//! ## Design Rationale
@@ -22,7 +22,7 @@
2222
//! before the Tokio runtime begins spawning tasks. `dev_log!` is synchronous,
2323
//! so the banner is guaranteed to land in the log before any extension code runs.
2424
//!
25-
//! Runtime overhead is zero all `env!(...)` invocations become string
25+
//! Runtime overhead is zero - all `env!(...)` invocations become string
2626
//! literals at compile time and are inlined into a single write call.
2727
//!
2828
//! ## References

0 commit comments

Comments
 (0)