Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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 .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Optional: zync-survey API base URL (no trailing slash).
# Dev default in code is http://127.0.0.1:8090 when unset.
# VITE_SURVEY_API_URL=https://survey.example.com
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ jobs:
GITHUB_TOKEN: ${{ env.RELEASE_GITHUB_TOKEN }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
# Baked into the Vite bundle for survey/feedback API (prod).
VITE_SURVEY_API_URL: ${{ secrets.VITE_SURVEY_API_URL }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
Expand Down
31 changes: 30 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ All notable changes to Zync are documented in this file. The format is based on

## [Unreleased]

## [2.26.1] - 2026-08-28

### Fixed
- **Survey upgrade check-in**: Existing users updating into a survey-enabled build see **Help Zync improve** once (new installs still get Welcome). Skip/Submit never re-prompts on later releases. Captures `lastSeenVersion` before What's New rewrites it so the upgrade modal is not skipped. ([d3f4060])
- **Status bar “No Connection” stacking**: Keep the idle connection label on one line (`whitespace-nowrap` + `shrink-0`) so it no longer wraps vertically when the status bar is tight. ([d3f4060])
- **Status bar host name width**: Cap the connection label at `8rem` with a fixed rem max (not a `%` of a squeezed flex parent), truncate with ellipsis, and keep the full name on hover — avoids both huge names and ~1-character crush. ([840afc2])
- **Survey API fetch hardening**: Accept any `127.x.x.x` loopback host for local `http`, and abort survey/feedback POSTs after 15s (including body read). ([193f568])

## [2.26.0] - 2026-08-28

### Added
- **Status bar SSH latency**: Live round-trip chip replaces the connected wifi icon (`21ms`), with Settings → Status Bar to toggle it. Probes the active SSH session only; fails soft when unknown. ([2608d05])
- **Branded connect stage**: Host connect uses a loaders kit (`ConnectLoader` / `ConnectStagePanel`) with a dash on the host tile, quiet Connecting… copy, and a fade into the workspace (or the same-frame error + Retry). ([12a8aa7])
- **In-app survey & feedback**: Optional welcome/update check-in popup and Settings → Feedback (private API submit + optional public GitHub issue). ([9903eb8])

### Fixed
- **Remote PTY / tmux initial size (#101)**: Terminal geometry is retained while the PTY is starting and flushed on `terminal-ready`, so tmux and other remote sessions fill the viewport on first attach without requiring a manual window resize. ([ff07642])
- **Survey API URL hardening**: Reject non-loopback `http` survey API bases, require HTTPS for remote hosts, refuse POST redirects, and persist discovery “Other” free text in prefs. Release builds bake `VITE_SURVEY_API_URL`. ([db0233d])

## [2.25.8] - 2026-08-22

### Added
Expand Down Expand Up @@ -1274,7 +1293,17 @@ Partial draft: desktop builds, AppImage Wayland strip, and APT `2.25.4` publishe
[6ed8f66]: https://github.com/zync-sh/zync/commit/6ed8f66
[db8442c]: https://github.com/zync-sh/zync/commit/db8442c
[7027a17]: https://github.com/zync-sh/zync/commit/7027a17
[Unreleased]: https://github.com/zync-sh/zync/compare/v2.25.8...HEAD
[2608d05]: https://github.com/zync-sh/zync/commit/2608d05
[12a8aa7]: https://github.com/zync-sh/zync/commit/12a8aa7
[9903eb8]: https://github.com/zync-sh/zync/commit/9903eb8
[ff07642]: https://github.com/zync-sh/zync/commit/ff07642
[db0233d]: https://github.com/zync-sh/zync/commit/db0233d
[d3f4060]: https://github.com/zync-sh/zync/commit/d3f4060
[840afc2]: https://github.com/zync-sh/zync/commit/840afc2
[193f568]: https://github.com/zync-sh/zync/commit/193f568
[Unreleased]: https://github.com/zync-sh/zync/compare/v2.26.1...HEAD
[2.26.1]: https://github.com/zync-sh/zync/compare/v2.26.0...v2.26.1
[2.26.0]: https://github.com/zync-sh/zync/compare/v2.25.8...v2.26.0
[2.25.8]: https://github.com/zync-sh/zync/compare/v2.25.7...v2.25.8
[2.25.7]: https://github.com/zync-sh/zync/compare/v2.25.6...v2.25.7
[2.25.6]: https://github.com/zync-sh/zync/compare/v2.25.5...v2.25.6
Expand Down
1 change: 1 addition & 0 deletions docs/SETTINGS_SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Zync provides an in-app editor tab (not external VS Code handoff) for global set
- app-wide appearance preferences (theme, global font family/size, accent)
- persisted feature/settings configuration in user `settings.json`
- update/config toggles and system-level options
- status bar preferences (Settings → Status Bar; `statusBar.showConnectionLatency`)

### What is currently “local” in UI terms (but not local override file scope)

Expand Down
5 changes: 4 additions & 1 deletion docs/TERMINAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,16 @@ Historical scrollback **does not reflow** when the window is resized. Lines writ

**Scheduler:** `createResizeScheduler` — 60ms trailing edge in `terminalFit.ts`.

**Desired-size flush (issue #101):** `syncTerminalResize` always records `desiredResize` from xterm. While the PTY is `starting` (or not yet spawned), IPC is deferred — size is **not** dropped. On `terminal-ready`, `flushTerminalResize` fits and sends the latest size (same pending→flush pattern as input). Manual window resize still force-syncs via `lastResize` clear.

### Regression watchlist (mitigated — re-check after layout/GPU changes)

| Symptom | Mitigation |
|---------|------------|
| Black margins around xterm | `.terminal-container` fill CSS, `safeFitTerminal` |
| Garbled text after resize | Trailing scheduler + post-fit refresh |
| PTY cols/rows drift | Hidden-tab gate; defer IPC until layout settle |
| PTY cols/rows drift | Hidden-tab gate; defer IPC until layout settle; desired-size flush on ready |
| tmux small until window resize (#101) | Retain `desiredResize` while starting; `flushTerminalResize` on ready |
| Double framebuffer | Single active renderer path (WebGL **or** DOM) |

---
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zync",
"private": true,
"version": "2.25.8",
"version": "2.26.1",
"type": "module",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zync"
version = "2.25.8"
version = "2.26.1"
description = "A modern SSH client"
authors = ["Gajendra"]
edition = "2021"
Expand Down
37 changes: 37 additions & 0 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4848,6 +4848,43 @@ pub async fn ssh_exec(
}
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ConnectionLatencyPayload {
pub connection_id: String,
pub rtt_ms: u64,
}

#[tauri::command]
pub async fn ssh_connection_latency(
id: String,
state: State<'_, AppState>,
) -> Result<ConnectionLatencyPayload, String> {
if id == "local" {
return Err("Local workspace has no SSH latency".to_string());
}

let session = {
let connections = state.connections.lock().await;
let conn = connections
.get(&id)
.ok_or_else(|| "Connection not found".to_string())?;
conn.session
.clone()
.ok_or_else(|| "Connection is not live".to_string())?
};

let rtt_ms = {
let handle = session.lock().await;
crate::connection_latency::measure_session_rtt_ms(&handle).await?
};

Ok(ConnectionLatencyPayload {
connection_id: id,
rtt_ms,
})
}

#[tauri::command]
pub async fn ssh_import_config(
app: AppHandle,
Expand Down
45 changes: 45 additions & 0 deletions src-tauri/src/connection_latency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use russh::client::Handle;
use std::time::{Duration, Instant};
use tokio::time::timeout;

use crate::ssh::Client;

const PROBE_TIMEOUT: Duration = Duration::from_secs(4);
const MAX_RTT_MS: u64 = 60_000;

/// Caps a measured duration so UI / IPC never see unbounded values.
pub fn clamp_rtt_ms(elapsed_ms: u128) -> u64 {
elapsed_ms.min(u128::from(MAX_RTT_MS)) as u64
}

/// SSH-layer RTT: time until a session channel is confirmed, then close it.
/// Does not run a remote command (no shell history / login noise).
pub async fn measure_session_rtt_ms(session: &Handle<Client>) -> Result<u64, String> {
let started = Instant::now();
let channel = timeout(PROBE_TIMEOUT, session.channel_open_session())
.await
.map_err(|_| "SSH latency probe timed out".to_string())?
.map_err(|error| format!("SSH latency probe failed: {error}"))?;
let rtt_ms = clamp_rtt_ms(started.elapsed().as_millis());
let _ = channel.close().await;
Ok(rtt_ms)
}

#[cfg(test)]
mod tests {
use super::{clamp_rtt_ms, MAX_RTT_MS};

#[test]
fn clamp_rtt_ms_keeps_typical_values() {
assert_eq!(clamp_rtt_ms(0), 0);
assert_eq!(clamp_rtt_ms(42), 42);
assert_eq!(clamp_rtt_ms(1_200), 1_200);
}

#[test]
fn clamp_rtt_ms_caps_unbounded_samples() {
assert_eq!(clamp_rtt_ms(u128::from(MAX_RTT_MS)), MAX_RTT_MS);
assert_eq!(clamp_rtt_ms(u128::from(MAX_RTT_MS) + 1), MAX_RTT_MS);
assert_eq!(clamp_rtt_ms(u128::MAX), MAX_RTT_MS);
}
}
2 changes: 2 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod ai;
mod atomic_io;
mod commands;
mod connection_latency;
mod fs;
mod ghost;
mod identity_migration;
Expand Down Expand Up @@ -176,6 +177,7 @@ pub fn run() {
commands::window_minimize,
commands::window_close,
commands::ssh_exec,
commands::ssh_connection_latency,
commands::ssh_import_config,
commands::ssh_import_config_from_file,
commands::ssh_import_config_from_text,
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "zync",
"version": "2.25.8",
"version": "2.26.1",
"identifier": "in.thesudoer.zync",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
Loading
Loading