Commit Graph

619 Commits

Author SHA1 Message Date
Dal Rupnik 2fb91b61cb fix(drop): propagate directory-read failures during drop copy
`copyTree`/`copyInto` treated `contentsOfDirectory` errors as an empty
listing (`try? … ?? []`), so a permission/I/O failure inside a dropped
folder was silently ignored and the drop reported success — producing an
incomplete copy in the guest with no warning (data-lossy for users who
expect the whole folder to transfer).

Let the read error propagate instead. copyTree's do/catch already removes
the partial `dst` and rethrows, so an unreadable subtree now aborts the
drop and cleans up rather than half-completing.

Adds a regression test covering an unreadable nested directory.
2026-06-08 09:29:12 +02:00
Dal Rupnik e78cb3c247 ci: retrigger Cirrus (unrelated testcontainers flake on prev run) 2026-05-20 11:28:48 +02:00
Dal Rupnik daed6eee82 fix: parse CRLF stdout from guest agent in GuestDropParseTests
split(whereSeparator: { $0 == "\n" || $0 == "\r" }) never matches CRLF
because Swift treats "\r\n" as a single Character (grapheme cluster
U+000D, U+000A), so the closure — which compares against the
single-codepoint characters "\n" and "\r" — fires for neither. With
CRLF input the entire stdout becomes one "line" that doesn't have the
tartdrop-dest= prefix, and the parser returns nil.

Switch to String.enumerateLines, which handles LF, CR, and CRLF.
2026-05-20 10:36:49 +02:00
Dal Rupnik 6375d224c1 fix: stop DropProgressCopier from tripping Swift's exclusivity check
The inner `report` closure captured `copied` from the enclosing scope, but
`copyFile`/`copyInto` hold the same variable as `inout` while running — so
the next progress callback overlapped the still-active inout access and
aborted the process with a fatal access conflict mid-drop. Pass the value
in instead of capturing it.
2026-05-20 10:15:08 +02:00
Dal Rupnik b954c0d56c feat: live byte progress and best-effort cancel for file-promise drops
Covers two of the file-promise trade-offs (A + B from the review):

A. Progress: poll bytes streamed into the (shared) per-item subdir while
   the opaque receivePromisedFiles runs, feeding the toast's live size
   readout instead of a dead spinner. Best-effort — providers that write
   a temp file and atomically rename only become visible at the end.

B. Cancellation: DropCancellationToken gains an onCancel hook; the
   promise path cancels its operation queue and unblocks the wait loop
   on ⊗ instead of sitting on the 30 s timeout. Cooperative providers
   abort; either way we stop waiting and clean up at once.

C (true determinate % via NSItemProvider.loadFileRepresentation) is
intentionally NOT implemented: macOS AppKit drags don't vend
NSItemProvider, and NSFilePromiseReceiver exposes no Progress/cancel
surface, so a real percentage isn't reachable without an undocumented
hack. The size poller is the honest ceiling.

Also: capture the dispatch group locally in RelocationGate.drain to drop
a Sendable-capture warning. Adds DropCancellationTokenTests.
2026-05-20 10:15:08 +02:00
Dal Rupnik c596672c5a refactor: receive file promises straight into the drop zone
Previously promised files (Photos/Mail/browser drags) were received into
a host-private staging dir and then copied a second time into the shared
drop zone. Now each promise is received directly into its per-item
dropRoot/<uuid>/ subdir, so the source app writes it exactly once.

Plain file/dir drags still stream through copyTree (progress +
cancellation). Promises show an indeterminate bar (no byte progress is
available from the promise API) and relocation uses the actual written
names so provider de-duplication is honored. relocate() now reaps the
subdir by emptiness rather than unconditionally, so a multi-file promise
sharing one subdir isn't deleted out from under its pending siblings.
2026-05-20 10:15:08 +02:00
Dal Rupnik 100fc1e8bc fix: harden drag-and-drop against the edge-case report
Introduces DropHandler to own the drop pipeline and addresses every item
from the review:

- Folders / .app bundles / packages: DropProgressCopier.copyTree walks
  directories instead of failing with a generic 'Failed to copy'.
- File promises (Photos, Mail, browser image drags) are now accepted and
  materialized instead of silently no-opping.
- Multi-file toast race: per-file DropSession id; stale relocation
  results for a superseded file are ignored by update/finish/
  setFinalDestination.
- Partial files: copyTree removes its partial output on any error, and
  the handler drops the now-empty subdir, so the guest never sees a
  truncated file.
- Teardown race: in-flight guest relocations register with
  RelocationGate; 'tart run' drains it (<=6s) before deleting the drop
  zone / exiting.
- Path collisions: each file copies into its own dropRoot/<uuid>/ subdir.
- Reserved name: a user --dir named 'Dropped Files' is now rejected.
- Linux / no agent: relocation is skipped and the toast says 'Copied to
  the shared folder' instead of a misleading Finder destination.
- Agent-down timing: success toast holds on a fallback timer that
  outlives the 5s RPC deadline so the final destination is always shown.
- Concurrency: relocations run one-at-a-time; copy failures are
  coalesced into a single alert instead of a modal storm.
- Zero-byte/unknown size shows just the copied amount, not '0 bytes of ?'.
- Removed dead DropFolderBox + stale doc comment; fixed the
  http://-vs-https:// typo in toRemoteOrLocalURL.

Tests: DropProgressCopierTests (replace/cancel/error-cleanup/empty/
directory/size), GuestDropParseTests (stdout parser), and a reserved-name
case in DirectoryShareTests. Product and test target both compile.
2026-05-20 10:15:08 +02:00
Dal Rupnik 3bae4c62cf style: apply SwiftFormat indent to GuestDropSynthesis.swift
Pre-existing 2-space indentation drift; no semantic change. Brings the
file in line with the project's mandated SwiftFormat config.
2026-05-20 10:15:08 +02:00
Dal Rupnik 7af5d839fa chore: fix DirectoryShareTests filename typo (Direcotry -> Directory) 2026-05-20 10:15:08 +02:00
Dal Rupnik 93d5708c08 refactor: split DropProgressToast into focused sub-250-line files
DropProgressToast.swift (436 lines) bundled four responsibilities. Split
verbatim into:

- DropCancellationToken.swift  - cancel token + DropCopyCancelled sentinel
- DropProgressCopier.swift     - chunked file copier
- DropProgressToast.swift      - toast state/coordination (lifecycle API)
- DropProgressToast+Panel.swift - AppKit panel construction/positioning

Each file is well under 250 lines and single-responsibility. The only
non-move change is widening the members the +Panel extension references
from private to internal; no behavior, signatures, or logic changed.
2026-05-20 10:15:08 +02:00
Dal Rupnik 9e5f65b8da feat: drop file into the Finder window under the cursor
Previously the guest-agent relocation script always asked Finder for the
*front* window and dropped there. That meant dropping on the bare Desktop
while a Downloads window happened to be frontmost put the file in
Downloads — the opposite of user intent.

Plumb the drop point (already captured as a normalized 0..1 top-left
coordinate by DropGeometry.normalize) through synthesizeGuestDrop and
GuestDropSynthesis.perform into the in-guest script. The script now
converts the point to guest-screen pixels using the desktop window's
bounds, walks `every Finder window` in front-to-back order, and returns
the folder of the first window whose bounds contain the point. If no
window does, dest_dir stays empty and the existing fallback drops the
file on ~/Desktop — which is correct for a drop on bare Desktop.

The AppleScript body is piped to `osascript -` via a single-quoted bash
variable (rather than a heredoc) so it survives Swift's multi-line
string indentation rules cleanly.
2026-05-20 10:15:08 +02:00
Dal Rupnik 0cc0969652 feat: show drop destination in toast and unblock guest agent on dev builds
After a host→guest drop completes, the toast now updates from "Done" to
"Copied to <Folder>" (e.g. "Desktop", "Documents") once the guest agent
has finished relocating the file out of the share. When the agent isn't
reachable, the toast falls back to "Copied to Shared Files" so the user
always sees a sensible destination.

Three things had to come together:

1. Toast surfaces the destination

   `GuestDropSynthesis.perform` now returns a `GuestDropOutcome` carrying
   the basename of the destination folder (the in-guest script appends
   a `tartdrop-dest=<basename>` line after `mv`). The relocation runs as
   a fire-and-forget Task off the copy queue and patches the toast via a
   new `setFinalDestination` method — which uses a shared
   `pendingFinalText` slot so a fast relocation result doesn't get
   clobbered by the delayed "Done" placeholder.

2. gRPC timeout shortened so failures fit in the toast window

   The exec-call timeout drops from 8 s to 5 s. Steady-state calls
   finish in well under 500 ms; 5 s leaves headroom for a first-run
   osascript blocked on a Finder Automation TCC prompt inside the
   guest. The baseline hide on success grows to 2 s so the destination
   update has a chance to land before the panel disappears.

3. Dev builds of tart can now host the guest agent

   `CI.version` returns `"SNAPSHOT"` for non-tagged builds, which gave
   the VM a console port named `tart-version-SNAPSHOT`. The guest agent
   parses that suffix as a semver and falls back to `unix.Kill(getppid,
   SIGTERM)` when it can't — which fails with EPERM against launchd and
   prints "operation not permitted" every 10 s forever. A new
   `CI.deviceVersion` always emits a valid semver with major ≥ 2
   (`"99.0.0"` for SNAPSHOT) and VM.swift uses it for the port name.
   `99.0.0` without a `-prerelease` suffix is required because macOS's
   BSD tty layer rejects the dotted+hyphenated form and refuses to
   expose the device under `/dev/cu.*`.
2026-05-20 10:15:08 +02:00
Dal Rupnik 2db423c404 fix: overlay drop toast inside VM window with fade-in and synced "Done"
Three related fixes to the drop progress toast:

1. Position the toast over the VM window's content (top-right, 44 pt
   below the titlebar) instead of floating outside above the window.
   This reverts the layout intent of the prior "float above" commit;
   the toast remains an NSPanel child-windowed to the VM window so it
   tracks z-order and movement.

2. Replace the broken slide-in animation with a fade-in. The previous
   animation called `panel.animator().setFrameOrigin(target)`, which is
   a silent no-op on NSWindow — the panel jumped to the off-screen-right
   start position and stayed there. Use `animator().alphaValue` instead,
   which is actually animatable on NSWindow.

3. Sync the "Done" text with the bar reaching 100%. NSProgressIndicator
   has an undocumented ~0.3 s smooth-fill animation when doubleValue
   jumps, so showing "Done" simultaneously with setting maxValue made the
   text lead the bar. Delay "Done" by 0.35 s and extend the hide delay
   to 1.05 s so "Done" still gets ~0.7 s of visibility. Also reset the
   bar in `hide` so a subsequent drop doesn't briefly flash the previous
   final state before resetting to 0.
2026-05-20 10:15:08 +02:00
Dal Rupnik 084e69b644 fix: float drop toast above the VM window, not inside it
The notification banner used to sit inside the VM window's top-right
corner, overlapping guest content. Move it outside the window: the
toast's bottom edge now sits 10 pt above the VM window's top edge,
still right-aligned. The slide-in animation is unchanged.

If the VM window is jammed against the top of the screen and there's
no room above for the toast, fall back to the old "inside top-right"
position so we never clip the menu bar.
2026-05-20 10:15:08 +02:00
Dal Rupnik 83be453200 feat: move drop toast to top-right and add cancel button
Two changes to the drop progress HUD:

  - Position: was bottom-center of the VM window, now top-right with a
    quick (~0.18 s) slide-in from the right edge — reads as a macOS
    notification banner. Subsequent files in a multi-file drop retarget
    the panel in place without replaying the animation.

  - Cancel: small ⊗ close button in the toast's top-right corner. Click
    flips a `DropCancellationToken` shared with the chunked copier,
    which polls between 1 MiB chunks and throws `DropCopyCancelled` on
    the next boundary (sub-100 ms latency on fast disks). The drop
    handler removes the half-copied destination, the toast shows
    "Cancelled", and any remaining files in a multi-file drop are
    skipped instead of starting.
2026-05-20 10:15:08 +02:00
Dal Rupnik 8c684f52e4 feat: show progress-bar toast during host→guest drop copy
`FileManager.copyItem` is opaque to the user: a 5 GB drop just freezes
the cursor for ten seconds with no visible signal that anything is
happening. Replace it with a chunked `FileHandle` copy (1 MiB chunks,
50 ms-throttled progress callbacks) and render a borderless HUD panel
anchored to the VM window bottom — filename, determinate bar, and a
"[i/N] copied / total" detail line. The panel auto-hides ~0.8 s after
the final byte so the user sees the "Done" state.

Multi-file drops reuse the same panel and increment the [i/N] counter,
since the existing copy loop already serializes files.
2026-05-20 10:15:08 +02:00
Dal Rupnik 6af39e807f feat: relocate dropped files to frontmost Finder folder, not the share
The previous guest-drop synthesis only ran `open -R` on the file's
location in the "Dropped Files" share. That just revealed it in the
share, which was the user-visible bug we were trying to fix: dropped
files appeared in `/Volumes/My Shared Files/Dropped Files/` instead
of somewhere natural.

Now the agent's exec script asks Finder for the frontmost window's
POSIX path via `osascript`, moves the file out of the share into that
folder, and then reveals it. If no Finder window is open, the path
isn't writable, or it points back at the share itself, the script
falls back to `~/Desktop`. Filename collisions get suffixed
(`foo.txt` → `foo 2.txt`, …) instead of clobbering.

osascript here runs in the agent's user-GUI session, so the first
drop triggers an Automation→Finder TCC prompt in the guest the user
approves once.

Verified end-to-end via `tart exec` (same RPC path the drop handler
uses): no Finder window → ~/Desktop, Finder on Downloads → Downloads,
Finder on Dropped Files itself → ~/Desktop fallback.
2026-05-20 10:15:08 +02:00
Dal Rupnik 474fd47b6f feat: scaffold guest-side drop synthesis (host plumbing only)
Adds the host-side hook for the forthcoming tart-guest-agent DragAndDrop RPC
that will perform a real drag-and-drop at the cursor's position inside the
guest instead of dumping files into a generic share folder.

- DropGeometry.normalize: pure helper turning a view-local drop point into
  top-left (0..1) coordinates the guest agent can map onto its screen.
- VMContainerView.performDragOperation: capture draggingLocation on the
  main thread (only valid here), compute normalized point, then dispatch
  the copy off-main as before.
- synthesizeGuestDrop: documented no-op stub called from the copy completion
  path. When the agent RPC lands, this becomes the real call site; until
  then it returns false and the existing share-folder copy is the
  user-visible result.

No behavior change for users today.
2026-05-20 10:15:08 +02:00
Dal Rupnik 2c5037ea50 refactor: harden drag-and-drop drop zone lifecycle and copy path
- Hold a FileLock on the dropzone so a concurrent tart command's
  Config.gc() can't delete it from under the running VM, and remove
  the directory explicitly on VM exit (defer doesn't fire through
  Foundation.exit).
- Extract DirectoryShare.collect() and add the drop zone as a named
  "Dropped Files" share so the share-builder logic stays simple.
- Copy dropped files on a background queue so large drops don't
  freeze the VM framebuffer (the view that handles the drop also
  renders the VM).
- Reject unnamed --dir combined with drag-and-drop with a clear
  error pointing at both fixes; document the same in --no-drag-and-drop
  help.
- Add DirectoryShare.collect() tests covering empty, named, drop-zone-only,
  unnamed-conflict, and custom-mount-tag cases.
2026-05-20 10:15:08 +02:00
Dal Rupnik 8546f07ea8 feat: Implement drag and drop functionality 2026-05-20 10:15:08 +02:00
Nikolay Edigaryev 5287b597a1
docs: clarify that nested virtualization is only for Linux VMs for now (#1233) 2026-05-12 21:45:18 +00:00
Fedor Korotkov 8aa377b71e
Skip integration test gate for release (#1229) 2026-04-11 22:33:36 -04:00
Fedor Korotkov 1e52e17c21
Move brew completions to post_install (#1227)
* Move brew completions to post_install

* Reduce Layerizer test disk fixture size to 1GB

* Skip registry integration tests on Docker startup failure
2026-04-11 22:26:52 -04:00
Nikolay Edigaryev d39f7c6036
Docker-related fixes (#1221)
* tests: fix RegistryRunner's "-p" specification passed to "docker"

* tests: "docker" binary is now installed from Homebrew
2026-04-09 21:00:36 -07:00
Fedor Korotkov abfbb10618
[docs] Add announcement about joining OpenAI (#1223) 2026-04-07 03:55:20 -07:00
Nikolay Edigaryev 094f850046
Add Liquid Glass icon and sign the whole app bundle (#1216) 2026-03-20 23:19:21 +01:00
Fedor Korotkov f1305dc083
Update FAQ for local network prompt (#1211)
* Update FAQ for local network prompt

* Apply suggestions from code review
2026-03-06 17:57:06 +00:00
Nikolay Edigaryev 605234b5dd
Mention macOS Tahoe everywhere instead of macOS Sequoia (#1208)
* Mention macOS Tahoe everywhere instead of macOS Sequoia

* Fix spurious rename
2026-03-02 08:50:29 -05:00
sneedandfeed be272d8abd
Replace Sequoia with Tahoe in Quick Start's first few instructions & add Tahoe to images available (#1206)
* update quick-start.md for tahoe

* oops

* I forgot this part.
2026-02-27 08:27:59 -05:00
Fedor Korotkov faa40b6832
Remove disk v1 support (#1204)
* Remove disk v1 support

* fix: address PR review feedback

- add explicit error for legacy disk.v1 media type during pull
- include actionable re-push guidance in runtime error

🤖 Generated with [Codex](https://chatgpt.com/codex)

Co-Authored-By: Codex <codex@openai.com>

* Re-use legacyDiskV1MediaType in error message

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com>
2026-02-25 14:34:25 +00:00
Nikolay Edigaryev d45ef38cf7
StdinCredentials: increase maxCharacters to 8,192 (#1203) 2026-02-23 18:55:52 +01:00
Nikolay Edigaryev e26b376d51
tart list: remove "SizeOnDisk" and add "Accessed" field (#1202)
* tart list: introduce "Accessed" field to show last accessed date of a VM

* tart list: remove "SizeOnDisk" field as it's unused
2026-02-23 18:55:36 +01:00
Nikolay Edigaryev 8f8a24ad19
Use ghcr.io/squidfunk/mkdocs-material:latest container for docs (#1201)
* Use ghcr.io/squidfunk/mkdocs-material:latest container for docs

* CI: use ghcr.io/squidfunk/mkdocs-material:latest too
2026-02-17 14:59:25 -05:00
Fedor Korotkov 29e0606ea3
Update yearly pricing docs (#1197)
* Update yearly pricing docs

* fix: clarify pricing update in 2023 licensing post
2026-02-13 15:46:01 +00:00
Nikolay Edigaryev 594c6d74cd
docs: migrate "Managing VMs" section to "Quick Start" (#1196) 2026-02-13 05:18:14 -05:00
Nikolay Edigaryev fc159c9992
docs: document TART_REGISTRY_HOSTNAME (#1195) 2026-02-12 21:56:24 +00:00
Nikolay Edigaryev 863e3c2925
Bind and connect to Unix domain sockets using relative paths (#1192) 2026-02-05 15:51:14 +01:00
Nikolay Edigaryev 372affb0dc
Switch back to github.com/open-telemetry/opentelemetry-swift upstream (#1189) 2026-02-02 19:40:24 +01:00
Nikolay Edigaryev 37b8219579
Switch to github.com/open-telemetry/opentelemetry-swift fork (#1186)
* Switch to github.com/open-telemetry/opentelemetry-swift fork

* Use cirruslabs-owned fork
2026-01-29 16:54:57 +00:00
Nikolay Edigaryev f1aa591935
OpenTelemetry: set default resources, service.name and service.version (#1184)
* OpenTelemetry: set default resources, service.name and service.version

* Ensure that service name and version resources are set
2026-01-27 16:17:05 +01:00
Fedor Korotkov 6189dc23af
Fix VM window not appearing on tart run (#1183)
Restore the applicationDidFinishLaunching method that was accidentally
removed in commit b1e88e1 ("tart run: do not remove 'Edit' menu as its
not present anymore").

That commit intended to remove the Edit menu removal code (since the
menu no longer exists), but also removed the crucial activation code:
- setActivationPolicy(.regular) - tells macOS this is a GUI app
- activate(ignoringOtherApps:) - brings the window to the foreground

Without these calls, the VM runs fine (SSH works) but no window appears
on screen.

Fixes #1181

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 15:16:33 -05:00
Fedor Korotkov 361465748b
Add build workflow for testing (#1180)
* Add build workflow

* Split build workflows

* Combine build workflows
2026-01-23 09:41:13 -05:00
Nikolay Edigaryev e0147448a8
OpenTelemetry: only initialize tracing when TRACEPARENT env. var. is set (#1182) 2026-01-23 14:57:53 +01:00
Nikolay Edigaryev 7038c45f8b
Switch to OpenTelemetry (#1179)
* Switch to OpenTelemetry

* Integration tests in Golang
2026-01-23 12:04:21 +01:00
Nikolay Edigaryev 44892c5def
Refactor "diskutil create" and "diskutil info" into a separate class (#1172)
* Show true ASIF disk sizes

* Use older sizeGB()
2026-01-22 13:00:27 +01:00
Nikolay Edigaryev 20dcfc83f2
Disable Sentry's app launch profiling (#1164)
And access SentrySDK only when SENTRY_DSN is set.
2025-11-10 23:50:32 +04:00
Fedor Korotkov c192de20f5
[docs] clarify licensing request details in press release (#1159) 2025-10-27 16:04:35 +00:00
Fedor Korotkov e28d9337a5
[docs] publish press release about licensing violation (#1158)
* [docs] publish press release about licensing violation

Highlighting that this is an exceptional case since the company did contact us about a license, and we explicitly declined due to conflict of interests.

* Fixed linting
2025-10-27 11:21:46 -04:00
Nikolay Edigaryev 68ffa6c5e4
tart set: support optional "pt" and "px" units for "--display" argument (#1155)
* tart set: support optional "pt" and "px" units for "--display" argument

* Don't forget to update "unit" too
2025-10-21 21:35:42 -04:00
Nikolay Edigaryev 1b091e9db0
tart run: introduce new "--net-softnet-block" command-line argument (#1156) 2025-10-21 21:14:43 +04:00