Architecture
The application is Go + MyGo native UI. VitePress is a separate documentation build and is not embedded as the application frontend.
State and ownership
main.go parses CLI options and passes name, version, config path and the public update configuration embedded from mygo.json into internal/app.Run. The app package owns native lifecycle, windows and UI tests. application owns the preference store and active window. window groups connection, transfer panel, preferences, saved locations and command palette state, plus feature-specific browser, object, bucket, RAM, mail and update state. Views read state declaratively; commands() defines both menu and command-palette actions.
Native locale and theme APIs run on the UI thread. Preference changes are written in the background, then the UI snapshot, language, appearance and custom menu are updated together.
Background work
Blocking work goes through window.run. Copy user inputs before launching work, support context cancellation, and publish results through Window.Update.
internal/tasks uses generation checks so late progress or completion callbacks cannot update a replaced task or closed window. The foundation serializes settings and browser actions. internal/transfers.Queue is a separate scheduler with pending lists and running counts for independent upload/download limits and one mutation slot, so browsing remains available during file transfer. AddBatch appends a selection before scheduling once, and completion only checks pending queue heads. Removed workers keep their slot until completion. Completed/skipped jobs release their work closures, and history compaction clears discarded pointers. Its state and callbacks are UI-owned; workers receive copied file paths, objects and service references. Cancellation reaches the worker context; retry starts after it stops. Disconnect resets the queue generation and releases job closures, so callbacks from old sessions cannot affect new transfers.
Persistence
config.Store validates schema version, language and appearance. A mutex protects snapshots. Writes use a temporary file, sync and rename; memory changes only after successful persistence.
Corrupt and unsupported settings are never overwritten automatically. Repair the file and use Reload settings. Missing language in an older schema-v1 file inherits system.
Internationalization
internal/i18n/locales/*.json is embedded into the Go binary. Semantic keys keep UI code independent of any source language. Resolution and fallback match the approach in rivu. See language and appearance.
OSS boundary
internal/ossclient wraps the official Alibaba Cloud OSS Go SDK v2 with HTTPS endpoint validation, AccessKey / STS signing, region-aware clients, pagination and sanitized errors. Within internal/app, connection_actions.go coordinates login and recovery; browser_state.go owns navigation history, listing merge and cached sorting; browser_actions.go coordinates navigation requests. The corresponding view files render the Design v2 surfaces. Preview, share, metadata and ACL fields are separated from the object dialog shell; object mutations have their own queue orchestration. The transfer adapter streams files with progress, offers explicit conflict policies, constrains download ETags when available and verifies size/CRC64 before publishing a local temporary file. SDK transfer managers provide multipart and checkpoint recovery. No-overwrite headers are subject to OSS bucket-versioning semantics. The UI must not call the SDK or persist secrets directly.
Design references and implementation milestones are tracked separately. The production app uses real OSS requests; fixture data exists only in tests. Account-level listing and direct bucket/prefix connections have separate permission requirements. Connection profiles persist without IDs, secrets or tokens. Sorting is local to loaded pages, while object prefix search is server-side.
Durable transfers and object operations
internal/transfers.Record stores one immutable operation description and its last outcome per JSON file. Files are atomically replaced with owner-only permissions before work and after completion. The connection profile and AccessKey ID are hashed to isolate recovery directories; neither secrets nor signed URLs are serialized. Restore presents unfinished entries as stopped and requires explicit retry. Recovery returns valid unfinished records together with per-file diagnostics; corrupt records remain on disk and do not prevent other jobs from being restored. SDK checkpoint directories belong to individual jobs; concurrent attempts cannot operate on the same checkpoint. A record lease spans worker execution and its final journal write. Explicit removal writes a tombstone and cleans the journal, checkpoint and private download staging directory after the worker exits. Terminal records are retained for seven days and pruned on connection; unfinished or corrupt records are never aged out. Failed cleanup leaves data for the next pruning pass.
Upload, download and recursive object planning live in internal/transfers, independently of MyGo. Folder planning rejects symlinks and non-regular input. Download records persist the canonical selected root; directory preparation, publication and cleanup use os.Root. Opening a persisted root rejects symlinks in its ancestors. SDK partials remain in application state, and verified data is copied to an exclusive temporary file inside the selected root before atomic publication. OSS keys are checked for traversal, reserved names and case collisions. Transfer jobs preserve the selected relative tree. Download SDK checkpoints verify saved ranges before reuse. Part data remains on cancellation; remote multipart cleanup is available through the confirmed bucket multipart dialog.
internal/vault wraps the OS keyring. Save/delete require explicit UI actions; keep-login authorizes a later keyring load and never fall back to plaintext. Tests use an in-memory keyring provider. internal/ossclient enforces authorization-code expiry, read-only restrictions and scoped roots independently of UI controls.
Object previews are bounded before decoding. Edited text defaults to a new key. Replacing the original is an explicit choice, with ETag preflight and preserved headers, metadata, ACL, tags and storage class; the preflight and PUT are not atomic. Metadata self-copy constrains the source ETag and preserves ACL and content headers. Move retries remember that a successful copy already happened; deletion remains a separate, non-atomic OSS operation. Object mutation jobs are session-only and are not replayed after restart.
Release checks use internal/updates with the configured MyGo feed/signature format and MyGo installation eligibility, otherwise the fixed GitHub repository. Automatic updates only stage and verify full archives; explicit installation re-verifies those bytes and supports rollback on replacement failure, followed by a separate MyGo restart action. No delta archives are used. Unconfigured development builds offer manual downloads. Update, thumbnail and object-detail tasks have separate cancellation generations. Settings use a separate category dialog with debounced draft validation and saving. RAM/STS use signed OpenAPI requests; SMTP enforces TLS. A bounded diagnostics worker persists operation status and error types without payloads or secrets.
Protocol references: OSS Uploader, PutObject, CopyObject, and the pinned upstream OSS Browser authorization-code format linked in design.
Listing and rendering cost
Pagination merges use a key set to deduplicate in linear time. Listing slices are replaced on refresh and merge; browser state caches sorted rows against listing identity, sort order and search. Transfer history and confirmation lists use ui.List with only visible rows built; history rows are keyed by job identity so filtering and deletion preserve the correct actions.
Version boundary
internal/buildinfo.Version is the desktop version source. The root entry passes it to app.Run, and tools/version checks or synchronizes it with mygo.json. CI, make check and the Release workflow all run the same check. The private root npm package has no desktop release version semantics.