Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ ShellCanvas follows semantic versioning for published SDK packages. Desktop rele
- Changed: **Apps** is now **App Manager**, a store-style manager with tiles, search, per-app details (status, source, approved access, update, disable and confirmed removal) and an **Add apps** page for GitHub and package-file installs.
- Added (app packages and SDK): optional one-line `description` and embedded `icon` fields. `shellcanvas-app build` embeds a PNG, JPEG, WebP or SVG icon of at most 256 KiB, and `shellcanvas-app init --description` and `repository` fill descriptions. Apps without an icon get a generic one. Packages that use the new fields need a desktop newer than 0.1.0; existing packages are unchanged.
- Fixed: installed apps opened in a workspace without an SSH target (connection adapters, the preview) now finish starting. Their environment event carried an undefined `host.target`, which the event journal rejected; the field is now omitted.
- Fixed: closing ShellCanvas while a local drive is attached no longer hides the window and leaves ShellCanvas and the Drive Bridge running in the background. Quitting is refused with a message until drives are detached, and no drive can start attaching once the window is closing.
- Fixed (Windows): drive letters held by remembered but disconnected network drives are no longer offered or accepted for drive attachment.
- Changed: the built-in Canvas theme (1.1.0) uses neutral white and charcoal surfaces with blue accents in both light and dark, replacing the green-tinted palette. Custom themes are unchanged.

## 0.1.1

Expand Down
78 changes: 64 additions & 14 deletions docs/filesystem-integration-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

Updated 2026-09-11. Goal remains active. This is not a release/completion claim.

Resumed after the Codex restart. The corrected isolated desktop build completed
successfully (3m 14s; executable timestamp 2026-09-11 01:43:23 local). After the
detach fix, the user confirmed the Windows flow worked end to end. This is manual,
user-reported acceptance of the attach/detach/quit checklist. Further testing is
stopped to conserve their OpenAI weekly quota; no additional CI was triggered. The
disposable test folder is `/tmp/shellcanvas-quit-check-Ijl8MnLz`. The desktop
changes are committed; the detailed local handoff is `.local/drive-bridge-resume.md`.

## Accepted scope

Implement optional core filesystem operations and machinery, with Windows
Expand All @@ -12,6 +20,44 @@ Include the dependencies' licensing/distribution limitations in that app.

## Current implementation

- Manual Windows testing found detach stuck on two handles on the empty fixture.
The bridge counted passive directory browsing as busy. Read-only directory
contexts now release their detach blocker; all file contexts (including mapped
files) and mutation-capable directory contexts remain blockers. Three focused
bookkeeping tests pass. The native regression now holds a browsing directory
across detach. It is now executed and passes in `native_windows_mount` when
`SHELLCANVAS_NATIVE_WINDOWS_TEST=1` and
`SHELLCANVAS_SKIP_PRIVILEGED_VOLUME_FLUSH=1` are set. Updated bridge binary
built 2026-09-11 01:49:46 local; the old isolated helper was stopped only
verifying its profile, W: target, and empty directory, to recover the stuck
test mount.
- WinFsp is installed on the development PC and its launcher is running. Local
native acceptance passes, including I/O, mapped files, metadata, native copy,
injected failures, busy-detach refusal, ordinary detach and pipe-loss cleanup.
This account cannot open a volume for privileged volume-wide flushing; the
explicit `SHELLCANVAS_SKIP_PRIVILEGED_VOLUME_FLUSH=1` test switch omits only
that subtest. Per-file flush remains exercised and the CI default still runs
the privileged check. No CI was triggered for this verification.
- A separate `dev.shellcanvas.driveprobe` profile exercised the actual Windows
file chooser (including cancellation), hash review, bridge installation,
attachment of a disposable SFTP folder on an authorized test host as W:, and
Open folder in Explorer. Native file creation, directory creation and Unicode-file rename
succeeded. Notepad opened and saved the remote text file; an independent SSH
read confirmed the exact saved text. The normal app profile was not changed.
Detach with open handles was refused with a visible explanation.
- The live quit test exposed a Tauri interaction: JavaScript `onCloseRequested`
destroys the window unless its callback prevents default, even if Rust blocks
close. The frontend now always prevents this automatic destruction and asks
the backend to close. The final backend check is serialized with attachment
reservation and marks closing before destruction so waiting attachments cannot
start afterwards. The subsequent attach/detach/quit checklist was confirmed
successful by the user; no further automated UI retest was run.
- Windows attachment selection and preflight now also reserve remembered,
disconnected network-drive letters via `WNetGetConnectionW`; active mount
checks alone omitted this PC's X: and Y:. The standalone bridge and its native
harness apply the same check. Mount cleanup deliberately still checks active
mounts, so a remembered network connection is not mistaken for a live bridge.
The updated bridge native suite passes locally (10.96 seconds).
- Unconfirmed SFTP CLOSE now retains cleanup ownership in a guard until the
server acknowledges success. Caller cancellation, rejection, timeout, or a
dropped cleanup task retires the mount's dedicated channel, releasing remote
Expand Down Expand Up @@ -282,9 +328,9 @@ Include the dependencies' licensing/distribution limitations in that app.
use isolated temporary profiles; no bridge was installed into the user's profile.
Three installer tests pass, including changed-review rejection preserving the
previous installation. Frontend production build, desktop clippy and 640px
preview overflow checks pass. Native chooser/approval still needs end-to-end use.
Native chooser/approval and an actual desktop-launched Windows mapping remain
unverified; the implementation is a development preview.
preview overflow checks pass. Native chooser/approval and a desktop-launched
Windows SFTP mapping subsequently passed in the isolated profile above;
the implementation remains a development preview until the other gates close.
- Files now offers **Attach to this computer…** for folders/current directories,
and an Attach action beside mounted volume locations. The dialog checks optional
provider support and installation, defaults to read-only, offers a Windows drive
Expand Down Expand Up @@ -373,10 +419,11 @@ Include the dependencies' licensing/distribution limitations in that app.
disposable FUSE test rather than changing the server's installed toolchain.
- Verified the official WinFsp 2.1.25156 MSI signature (Navimatics). Installation
failed with Windows Installer 1925 / exit 1603: administrator privileges needed.
No successful driver installation on this PC has been established. CI now
The user subsequently installed WinFsp successfully; local acceptance is recorded
above. CI also
installs the official runtime on its disposable Windows runner, checking the
pinned MSI SHA-256 and Authenticode signer before native tests. The local UAC question
is pending. This is an OS privilege issue, not an automatic approval rejection.
pinned MSI SHA-256 and Authenticode signer before native tests. The earlier
installer failure was an OS privilege issue, not an automatic approval rejection.
Installer/log are under `.local/bridge-tools`. Workspace-only libclang 18.1.1
is available at `.local/bridge-tools/python/clang/native` for Windows builds.

Expand Down Expand Up @@ -426,10 +473,11 @@ driver, remote account, normal app profile, or CI run was used by these checks.

## Remaining completion gates

1. Native end-to-end installation verification and an actual desktop-created
mapping, including chooser cancel, missing driver and changed executable.
The manager/UI/source leases are implemented; native user-flow verification
remains, including disconnect/source replacement/quit with busy local files.
1. Native chooser cancel, installation, desktop-created SFTP mapping, Explorer
launch, Notepad save and busy-detach refusal now pass on this PC. Remaining:
changed-executable/source-retirement and source-replacement user flows, plus
full failure recovery. The corrected ordinary detach/quit flow has subsequent
user-reported end-to-end acceptance.
2. Open folder and explicit Retry cleanup controls are implemented. Retry retains
the original helper/job ownership and connection reservation. After process
shutdown, native OS mount-table checks must confirm the location is unmounted;
Expand All @@ -454,10 +502,10 @@ driver, remote account, normal app profile, or CI run was used by these checks.
chooser/source-retirement race remains to be exercised without touching the
normal user profile.
4. Native Windows file API, replacement-save, capacity, basic error and busy-detach
checks now pass in disposable WinFsp CI. Remaining: desktop-created SFTP mapping,
Explorer/ordinary editor acceptance (native directory rename/open-handle
semantics are now verified above),
disconnect behavior and failure recovery. Local driver setup is still pending.
checks pass in disposable WinFsp CI and locally. Local driver setup and the
desktop-created SFTP/Explorer/Notepad path are now verified above. Remaining:
full desktop failure recovery. The corrected Windows lifecycle checklist has
user-reported end-to-end acceptance; wider failure matrices remain unverified.
5. Native Linux FUSE file operations, directory rewind/rename, truncate, flush,
mapped files, busy detach and ordinary unmount now pass as root and as an
unprivileged user on Linux 6.8. Pipe loss with an open file also passes in both
Expand All @@ -467,6 +515,8 @@ driver, remote account, normal app profile, or CI run was used by these checks.
other supported runtimes. Do not infer those from one kernel/runtime.
6. Verify modern macOS compilation and native runtime as available. fuser's
kernel/libfuse backend is implemented; FSKit operation is not established.
On this Windows machine, modern macOS runtime verification cannot be
executed yet, so this gate remains unverified here.
Do not claim an OS version/runtime works solely because Linux compiled.
7. Resolve currently documented limits before calling the release ready:
Windows cleanup-time deletion warnings and busy-detach control are implemented
Expand Down
7 changes: 4 additions & 3 deletions docs/themes.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Themes and appearance

Open **Desktop settings → Desktop**. Choose a theme and **Light**, **Dark**, or
**Follow system**. Canvas, the built-in theme, has a pale alpine daytime palette
and a deep blue-green evening palette. Changes apply to open windows, menus,
shared dialogs, the editor and the terminal without reconnecting hosts.
**Follow system**. Canvas, the built-in theme, uses neutral white and charcoal
surfaces with blue accents, inspired by macOS and Windows 11. Changes apply to
open windows, menus, shared dialogs, the editor and the terminal without
reconnecting hosts.

The same page controls the wallpaper, interface scale (80–150%), top toolbar
height and dock icon size. Scaling changes text, controls and spacing through
Expand Down
94 changes: 47 additions & 47 deletions examples/themes/canvas-study/shellcanvas.theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,38 @@
"format": 1,
"kind": "shellcanvas-theme",
"id": "example.canvas-study",
"version": "1.0.0",
"version": "1.1.0",
"name": "Canvas Study",
"author": "Your name",
"description": "A starting point for your own light and dark desktop theme.",
"variants": {
"dark": {
"colors": {
"base": "#122330",
"surface": "#1b2b36",
"raised": "#263943",
"inset": "#15242f",
"text": "#e0e9ea",
"muted": "#a1b8c2",
"accent": "#a3d9c4",
"onAccent": "#173a30",
"border": "#405660",
"hover": "#2b414b",
"selection": "#35564f",
"base": "#19191c",
"surface": "#242427",
"raised": "#303034",
"inset": "#1d1d20",
"text": "#f2f2f5",
"muted": "#b0b0ba",
"accent": "#7ab8ff",
"onAccent": "#102745",
"border": "#48484f",
"hover": "#39393f",
"selection": "#293f5b",
"danger": "#f1a7a7",
"dangerSoft": "#452f36",
"warning": "#e5c68a",
"warningSoft": "#3f3a30",
"success": "#a3d9b8",
"shadow": "#07121b",
"scrim": "#07121b",
"terminal": "#111d27",
"terminalText": "#d4e2e7",
"sky": "#193e50",
"glow": "#8cad9f",
"far": "#54757b",
"mid": "#304f5d",
"near": "#18333f"
"dangerSoft": "#462d33",
"warning": "#f2c572",
"warningSoft": "#413527",
"success": "#91d6a3",
"shadow": "#08080d",
"scrim": "#111118",
"terminal": "#161619",
"terminalText": "#e5e5ea",
"sky": "#102655",
"glow": "#536dd4",
"far": "#455b96",
"mid": "#293c72",
"near": "#172345"
},
"windowRadius": 14,
"windowHeader": 43,
Expand All @@ -45,31 +45,31 @@
},
"light": {
"colors": {
"base": "#e9eeeb",
"surface": "#f8faf7",
"raised": "#edf2ee",
"inset": "#e5ece7",
"text": "#223b3c",
"muted": "#516b6c",
"accent": "#296e60",
"base": "#f0f0f3",
"surface": "#fafafa",
"raised": "#f0f0f3",
"inset": "#e8e8ed",
"text": "#202024",
"muted": "#585861",
"accent": "#005fb8",
"onAccent": "#ffffff",
"border": "#c0d0c8",
"hover": "#e1eae3",
"selection": "#c8e1d4",
"border": "#c9c9d1",
"hover": "#e2e2e8",
"selection": "#d6e7fb",
"danger": "#a02f3d",
"dangerSoft": "#f8e6e5",
"warning": "#795916",
"warningSoft": "#f3ead5",
"success": "#2e7451",
"shadow": "#3c5854",
"scrim": "#304d47",
"terminal": "#f3f7f1",
"terminalText": "#243c36",
"sky": "#d4e4df",
"glow": "#fff0cb",
"far": "#9fbdb8",
"mid": "#799f9b",
"near": "#527e7c"
"warning": "#805400",
"warningSoft": "#fff1d6",
"success": "#26733b",
"shadow": "#252535",
"scrim": "#242432",
"terminal": "#ffffff",
"terminalText": "#242429",
"sky": "#dde8ff",
"glow": "#f4edff",
"far": "#a6b7e2",
"mid": "#748dc3",
"near": "#3b558c"
},
"windowRadius": 14,
"windowHeader": 43,
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ rand = "0.10"

[target.'cfg(windows)'.dependencies]
windows-core = "0.61.2"
windows = { version = "0.61.3", features = ["Win32_Foundation", "Win32_Graphics_Gdi", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Com_StructuredStorage", "Win32_System_Ole", "Win32_System_Memory", "Win32_System_DataExchange", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows = { version = "0.61.3", features = ["Win32_NetworkManagement_WNet", "Win32_Foundation", "Win32_Graphics_Gdi", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Com_StructuredStorage", "Win32_System_Ole", "Win32_System_Memory", "Win32_System_DataExchange", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }

[target.'cfg(target_os = "macos")'.dependencies]
libc = "0.2"
7 changes: 5 additions & 2 deletions src-tauri/src/drive_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,12 @@ pub async fn attach_drive(
// Reserve while holding the registry lock: disconnect/replacement use the same
// order and cannot retire this source between validation and grant ownership.
let _transition = state.mount_transition.lock().await;
if *_transition {
return Err("ShellCanvas is closing; no new drive can be attached.".into());
}
// Read the local OS table before reserving, without probing filesystem contents.
if crate::local_mounts::occupied(&target)? {
return Err("That local location is already mounted. Choose another location.".into());
if crate::local_mounts::reserved_for_attachment(&target)? {
return Err("That local location is already mounted or reserved. Choose another location.".into());
}
let registry = state.registry.lock().await;
let session = registry
Expand Down
29 changes: 28 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ use workspace_services::WorkspaceServices as ActiveSession;
#[derive(Default)]
struct DesktopState {
mappings: drive_mappings::Mappings,
mount_transition: Mutex<()>,
// True once final window destruction has been requested; attachments waiting
// for this lock must not start after the UI has gone away.
mount_transition: Mutex<bool>,
directories: directories::DirectoryReaders,
registry: Arc<Mutex<SessionRegistry<ActiveSession>>>,
next_id: AtomicU64,
Expand Down Expand Up @@ -730,6 +732,30 @@ async fn close_terminal(
Ok(())
}

/// The frontend always prevents Tauri's automatic destroy. Serialize the final
/// close with attachment reservation so a newly starting mount cannot lose its UI.
#[tauri::command]
async fn request_app_close(
window: tauri::Window,
state: State<'_, DesktopState>,
) -> Result<(), String> {
let mut closing = state.mount_transition.lock().await;
if *closing {
return Ok(());
}
if state.mappings.has_running() {
use tauri::Emitter;
let _ = window.emit("drive-mappings-close-blocked", ());
return Err("Detach local drives in Settings → Files before quitting ShellCanvas.".into());
}
*closing = true;
if let Err(error) = window.destroy() {
*closing = false;
return Err(error.to_string());
}
Comment thread
Copilot marked this conversation as resolved.
Ok(())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
Expand Down Expand Up @@ -794,6 +820,7 @@ pub fn run() {
}
let handler: fn(tauri::ipc::Invoke<tauri::Wry>) -> bool = tauri::generate_handler![
client_platform,
request_app_close,
drive_mappings::cancel_drive_startup,
drive_bridge_install::drive_bridge_installation,
drive_bridge_install::review_drive_bridge,
Expand Down
Loading