Repository navigation
emrg: the GUI shows no time on any message, though the daemon sends the moment #2340
Copy link
Copy link
Closed
Description
Origin: rant 2026-10-17T10:03:08.155490+08:00
No message row in the GUI shows a time: not the user's own line, not the assistant reply. The TUI half of rant 2026-10-09T09:25:00 landed in #1978 — the daemon stamps a moment once and sends it on the frame that carries the message — but the GUI throws that value away on both paths, so a GUI user cannot tell when anything was said.
What is broken, and where
The daemon side is already correct and is not part of this change:
donecarriestimestamp—emrg/server/daemon.py:4623(and the parked-injection path at:4833).user_messagecarriestimestampandrequest_id—daemon.py:4115.steer_committedcarriestimestamp—daemon.py:3738.- The persisted record carries the same string (
daemon.py:4113,:4600).
The GUI drops it at three seams:
- The frame never arrives.
emrg/gui/daemon_client.jshas no branch foruser_message, so it falls into the unknown-frame catch-all and is logged, not forwarded (test/daemon_client.test.jseven lists it as deliberately not forwarded). The user row is drawn optimistically by the Composer, and nothing ever tells it the daemon's moment. - The model has no field for it.
lib/transcript.ts—DoneDatais{request_id, timeout, content},UserEntryis{kind, text},AssistantEntryis{kind, rid, segments}. Even the frames that do arrive cannot be stored. - The view has no element for it.
components/TranscriptView.tsxrenders no time on a message row; only tool rows show· Ns, which is a duration, not a moment.
Replay drops it a fourth time: lib/historyReplay.ts types timestamp?: string on the record but the sink it feeds (addUserMessage(text, sid), handleDone({request_id})) has nowhere to put it, so a reopened session would show nothing even once the live path is fixed.
What this changes
- One formatter,
formatMessageTimeinlib/utils.ts, mirroring the TUI'sformat_message_time(emrg/client/python_tui/widgets/chat_row.py:34):HH:MMfrom the record's own clock,""for an absent or unreadable value — never an empty pair of brackets, neverInvalid Date, neverNaN. The full stamp is the element'stitle. - The moment travels from the daemon only.
DoneDatagainstimestampand the store stamps the reply's last assistant entry from it;user_messageis forwarded by the daemon client and the bridge stamps the user row with therequest_idit names;steer_committedstamps a queued row the same way. The client never stamps its own clock. - Replay carries it too, so the live row and the reopened record show the same value (the invariant emrg: each message shows the moment it was written #1978 established for the TUI).
- The scope is the GUI only (the rant's explicit boundary):
emrg/client/**is not touched.
Acceptance
formatMessageTimeanswersHH:MMfor a stored moment and""for absent/unreadable input, with a test over both directions.- A GUI test reads the rendered output: a user row and an assistant row each carry the time text; a row whose moment is missing renders no time element at all.
- Mutation-verified: deleting the
timestampfrom thedoneframe, from the user row's stamp, or from the replayed record turns those tests red, and changing the value changes what is rendered. - Live and reopened agree: the same message replayed from its record renders the same
HH:MMas the live row.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
argszero commentedon Oct 17, 2026
Handled by #2341