v0.14.1

feder-cr/AIHawkv0.14.1Sep 8, 2026by feder-cr

AI Summary

This release fixes critical bugs related to WebSocket reconnection logic and accessibility. It stops the client from replaying the entire conversation history upon reconnection, introduces event IDs for resumption, fixes a token meter bug, and improves the accessibility of the composer interface.

Key Highlights

  • Fixes reconnection logic to resume from the last received event instead of replaying the entire conversation history.
  • Adds event IDs with epochs to the history to support proper resumption after restarts or resets.
  • Fixes a bug where the token meter listener was missed after a turn ended.
  • Fixes a bug where the composer remained stuck on 'queue for next turn' after a process restart.
  • Improves accessibility by converting a clickable `span` to a proper `button` and adding a hidden label to the placeholder.

New Features

  • Introduction of numbered event IDs (with epochs) for the transcript history to enable stateful resumption.

Full Release Notes

**Fixed.** A reconnection replayed the whole conversation instead of resuming it. `EventSource` reconnects by itself after any drop, and the server answered every reconnection with the entire transcript, while the page - which has no de-duplication and had never been given an id to resume from - appended a second copy of everything. Measured before the fix: three consecutive subscriptions each received all 21 events of the same conversation, and not one carried an `id:`.

Events that go into the history are numbered now, and a reconnection is answered from where it got to. State events stay unnumbered on purpose: an id moves the resume point, and `busy` or `usage` are not places to resume from. The id also carries an epoch, because a position only means something inside one transcript - after a reset, or after the process restarts, the same index points at something else, so a mismatch replays from the beginning and says so first, and the page drops what it was holding instead of growing a chimera of two conversations.

**Two more of the same family**, found by reading rather than by a failure. The token meter never reached a listener that arrived after a turn had ended, on the one screen whose whole job is to say how big the transcript has become; the current reading is now sent at subscribe time. And a page reconnecting to a restarted process kept believing in the run that died with it, leaving the composer stuck on "queue for next turn" forever.

**Accessibility.** The queued-message chip was a `span` with an `onclick`, so pulling a queued message back into the composer needed a mouse: no tab stop, no Enter, no name. It is a button now. The composer's placeholder was its only hint and drew at 2.2:1 against the 4.5:1 text has to meet, so it moves one step up the ink scale and the field gains a hidden label; the step numbers keep the dimmer ink, which is what it was chosen for. And the page now declares its language, which it never did.