Follow-up to the previous pass, each with a test that fails without it.
- Cancelling a run also sets resume_blocked, so filtering on that flag
alone dropped the steps and tokens of cancelled work while still
counting the run and its duration. Only a run a later run resumed
from is excluded now: the resume claim leaves output_dir intact,
cancelling clears it, which tells the two apart.
- A fixed getTimezoneOffset() was applied to every historical message,
so winter records read an hour out when the panel is opened on summer
time, moving messages near midnight onto the wrong day. The endpoint
now takes an IANA timezone and converts each timestamp with its own
offset, keeping the fixed offset as the fallback.
- A fork with no new turn was skipped before its thread was registered,
so it did not appear in the chat total until its first message. The
thread is now counted before the cloned rows are suppressed.
- Routed and aliased responses record the model that actually answered
in responseDetails.responseModelId, while contextUsage.modelId stays
the requested checkpoint. Most used models now prefers the former.
- Renamed runs showed the model label instead of the name the user
chose. The name leads and the model moves beside the dataset.