diff --git a/client.go b/client.go index ccbf598..9b96b69 100644 --- a/client.go +++ b/client.go @@ -178,9 +178,15 @@ func (c *Client) Tick() { } func (c *Client) tick() { - // Batch: compute diff from accumulated actions. + // Only create a new state when the server has acked the previous one. + // This matches the Dart mosh client's behavior: at most one unacked + // state in flight. Keystrokes accumulate until the ack arrives, then + // the next tick sends them all as a single new state. + // Without this check, SetPending resets diffSent, causing Tick() to + // increment sentNum for the same cumulative payload — the server + // applies duplicate keystrokes. c.actionsMu.Lock() - if c.dirty { + if c.dirty && c.transport.AckedByRemote() >= c.transport.SentNum() { c.dirty = false c.processAcksLocked() newActions := c.actions[c.ackedActionCount:] diff --git a/cmd/mosh-wasm/main.go b/cmd/mosh-wasm/main.go index 5a33371..fe4289e 100644 --- a/cmd/mosh-wasm/main.go +++ b/cmd/mosh-wasm/main.go @@ -84,19 +84,12 @@ func newSession(url, key string) (*session, error) { func (s *session) jsObject() js.Value { obj := js.Global().Get("Object").New() - // Tick immediately when idle (no unacked states). When busy (ack - // pending), queue keystrokes and let the interval flush them. - // This gives instant feel for the first key while preventing - // cumulative state duplication during rapid typing. obj.Set("send", js.FuncOf(func(this js.Value, args []js.Value) interface{} { if len(args) < 1 { return nil } s.client.Send([]byte(args[0].String())) - t := s.client.Transport() - if t.AckedByRemote() >= t.SentNum() { - s.client.Tick() - } + s.client.Tick() return nil })) @@ -105,14 +98,11 @@ func (s *session) jsObject() js.Value { return nil } s.client.Resize(uint16(args[0].Int()), uint16(args[1].Int())) - t := s.client.Transport() - if t.AckedByRemote() >= t.SentNum() { - s.client.Tick() - } + s.client.Tick() return nil })) - // tick() — send keepalive. Called by JS setInterval at a slow rate. + // tick() — flush pending keystrokes + keepalive/retransmit. obj.Set("tick", js.FuncOf(func(this js.Value, args []js.Value) interface{} { s.client.Tick() return nil