Build Transmit

YESback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A two-pane transfer client that connects over SFTP and to S3-compatible object storage, keeps encrypted bookmarks, queues transfers that survive a dropped connection and resume rather than restart, verifies what it uploaded, and never silently overwrites a newer file.

estimated effort weekendthe files for this build are in the project pack

LanguageSwift 6 and SwiftUI in XcodeSFTPlibssh2 through a thin Swift wrapperObject storageThe S3 API over HTTPS with signature version 4CredentialsThe macOS keychain, never a settings fileQueueA persisted transfer queue with byte-level resume

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree if you own one

    Why The native panel, keychain and drag and drop are platform APIs.

    Get it Apple menu, then About This Mac.

    Verify About This Mac reports macOS 14 or higher

  2. installfree

    Why Builds the application and links libssh2.

    Get it Mac App Store or developer.apple.com/xcode. open ↗

    Verify xcodebuild -version prints 16 or higher

  3. have readya few USD a month, or free on hardware you own

    Why Local testing hides authentication, host key and timeout behaviour. You need somewhere you can also change the host key deliberately in Phase 1.

    Get it Any small virtual server with SSH, or a machine on your network. You must be able to regenerate its host key to test the refusal path.

    Verify You can connect and can change the host key on purpose

  4. API keystorage is cheap; some providers charge for requests and egress

    Why Phase 3 needs a real bucket. Providers differ subtly in their S3 implementations, so test against the one you will actually use.

    Get it Create a bucket with any S3-compatible provider and credentials scoped to it. Confirm it supports multipart upload before relying on it.

    Verify A signed list request returns the bucket contents

  5. have readyfree

    Why Resume and multipart both need a file large enough to interrupt partway, which a small file does not give you.

    Get it Generate a file comfortably above your multipart threshold. Keep it as a fixture.

    Verify A file large enough to interrupt mid-transfer is available

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- bookmarks(id, name, protocol, host, port, username, remote_path, local_path, keychain_ref) · never a password field
- transfers(id, bookmark_id, direction, local_path, remote_path, bytes_total, bytes_done, state, attempts, error, queued_at)
- conflicts(transfer_id, reason, local_mtime, remote_mtime, local_size, remote_size, resolution)
- verifications(transfer_id, algorithm, local_digest, remote_digest, verified_at)
- The queue is persisted, so quitting mid-transfer and relaunching resumes rather than starting again.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
BOOKMARKS_DB_PATHrequired~/Library/Application Support/Transfer/bookmarks.dbBookmarks and the transfer queue. Contains no secrets by design.
HOST_KEYS_PATHrequired~/Library/Application Support/Transfer/known_hostsStored host keys. A mismatch here is a refusal, not a warning.
MULTIPART_THRESHOLD_MBrequired64Files above this upload in parts. Below it, a single request is simpler and faster.
TRANSFER_MAX_ATTEMPTSrequired5Retries before a transfer is marked failed with its error preserved.
VERIFY_AFTER_TRANSFERrequiredtrueWhether to compare digests after transfer where the protocol allows it.

The build, in order

  1. Connect and list, with host keys taken seriously

    You can browse a remote server, and an unexpected host key stops you rather than warning you.

    1. Key and password authentication, directory listing with sizes and modification times.

      Files SFTP.swift

    2. First connection stores; later connections compare. A mismatch is a refusal.

      Files HostKeys.swift

    3. Bookmarks store a keychain reference, never a secret. Grep your settings file to prove it.

      Files Credentials.swift

    done when · tick each as it passes
    watch out
    • A changed host key is what a machine-in-the-middle attack looks like, and a warning dialogue with a continue button trains people to ignore exactly the signal that matters. Refuse the connection and require a deliberate action in settings to accept a new key, which is what a careful client does.
  2. Transfers that resume

    A transfer interrupted at eighty percent continues from eighty percent.

    1. Written to the database as transfers progress, so a crash loses at most a small window.

      Files Queue.swift

    2. Check the remote size and, where possible, the overlapping bytes. Trusting the offset alone is how corruption happens.

      Files Resume.swift

    3. Exponential backoff, a cap, and the error preserved for the user rather than retried forever.

      Files Queue.swift

    done when · tick each as it passes
    watch out
    • Resuming an upload by appending without verifying what the server already has will corrupt the file if the partial upload ended mid-write. Check the remote size before resuming and, where the protocol allows, verify the overlapping region rather than trusting the offset.
  3. Object storage as a second protocol

    An S3-compatible bucket behaves like a folder, with its differences made visible.

    1. Signing is fiddly and a wrong signature returns an unhelpful error. Test against a known request before building on it.

      Files S3/Sign.swift

    2. Above a threshold, upload in parts. Abort incomplete uploads on failure, because unfinished parts are billed and invisible.

      Files S3/Multipart.swift

    3. Show the object count before a prefix delete. This is not a directory and the interface should not fully pretend it is.

      Files S3/Delete.swift

    done when · tick each as it passes
    watch out
    • Object storage has no directories: a folder is a shared key prefix, and an empty folder does not exist at all. Presenting prefixes as folders is right, but deleting a folder means deleting every object under it, which is not the same operation as removing an empty directory. Say so before performing it.
  4. Conflicts and verification

    Nothing is overwritten by accident, and you know the upload arrived intact.

    1. Compare size and modification time. Default to refusing, never to overwriting.

      Files Conflicts.swift

    2. Sizes and timestamps for local and remote. The user decides; the client does not guess.

      Files ConflictSheet.swift

    3. A digest comparison where available. Test with a deliberately corrupted transfer to confirm it fails.

      Files Verify.swift

    done when · tick each as it passes
    watch out
    • Comparing modification times across systems is unreliable: clocks differ, timezones are handled inconsistently, and some servers report second precision while others report better. Treat a newer remote file as a conflict to surface rather than a fact to act on, and show both timestamps so the user can judge.
  5. Synchronising a folder

    You can mirror a local folder to a remote one and know exactly what will change first.

    1. Compare listings, produce uploads, updates and deletions as a plan before doing anything.

      Files Sync.swift

    2. Their own count and their own confirmation. This is the operation that causes the losses.

      Files SyncSheet.swift

    3. Patterns applied before the plan is built, so excluded files never appear in it.

      Files Sync.swift

    done when · tick each as it passes
    watch out
    • A sync with deletion enabled and a mis-set local path will cheerfully empty a remote folder, and the operation is fast. Always dry run first, show the deletion count prominently, and require confirmation specifically for deletions rather than for the sync as a whole.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more productivity utilities.