fix key repeat: only create new state when previous is acked (match Dart client)

The Dart mosh client only calls sendNew() when hasPendingState is false.
The Go client was calling SetPending on every tick with dirty=true, which
reset diffSent and caused new sequence numbers for the same cumulative
payload. Now tick() checks AckedByRemote >= SentNum before calling
SetPending — at most one unacked state in flight, matching the Dart
client's behavior.
This commit is contained in:
realrasengan
2026-03-30 19:56:45 -07:00
parent 2939a82eca
commit b71a9c5824
2 changed files with 11 additions and 15 deletions
+8 -2
View File
@@ -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:]
+3 -13
View File
@@ -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