Skip to content

feat: show message info on click - #2802

Merged
esokullu merged 7 commits into
webbrain-one:mainfrom
alectimison-maker:feat/message-info-pills
Aug 16, 2026
Merged

feat: show message info on click#2802
esokullu merged 7 commits into
webbrain-one:mainfrom
alectimison-maker:feat/message-info-pills

Conversation

@alectimison-maker

Copy link
Copy Markdown
Contributor

Summary

  • reveal a sent-time row when a user or assistant message is clicked
  • add verbose-only generation speed, output-token, duration, and stop-reason pills when those metrics are available
  • preserve message timestamps and assistant completion metadata across sidepanel restores in both Chrome and Firefox

Motivation

Message bubbles currently provide no way to inspect when a message was sent or, in verbose mode, how the assistant response was generated. This implements the compact/verbose behavior requested in #2798 without coupling the UI to trace storage.

Design

  • A pure message-info.js seam normalizes provider usage aliases, aggregates multi-call assistant turns, and returns localized pill descriptors.
  • The agent emits aggregate, app-owned scalar metadata through the existing run journal.
  • The sidepanel serializes that metadata with the message DOM, restores click and keyboard bindings, and keeps normal mode limited to the UTC sent timestamp.
  • Unknown timestamps from pre-feature restored messages stay unknown rather than being replaced with the panel-open time.

Testing

  • npm run test:toolbar-guard — 33 passed
  • node test/run.js — 1754 passed
  • npm run test:security — 60 passed
  • Loaded src/chrome as an unpacked extension in headed Chromium and verified click, Enter/Space toggle, normal/verbose rendering, restored binding, and clean console/page errors

Compatibility and risk

  • Chrome and Firefox implementations are mirrored; the pure formatter is byte-identical.
  • All 23 locales include fallback keys; English placeholders follow the repository localization policy.
  • No dependency, permission, manifest, network, provider API, or storage-schema change.
  • Firefox was covered by mirrored automated checks but was not manually launched.

Closes #2798

@vercel

vercel Bot commented Aug 15, 2026

Copy link
Copy Markdown

@alectimison-maker is attempting to deploy a commit to the esokullu's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds inspectable message timestamps and verbose assistant-generation metrics across Chrome and Firefox.

Changes:

  • Adds click and keyboard-accessible message-info rows.
  • Aggregates generation tokens, duration, speed, and stop reason.
  • Persists timestamps and completion metadata across sidepanel restores.

Reviewed changes

Copilot reviewed 50 out of 55 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/chrome/src/agent/agent.js Publishes aggregate completion metrics.
src/chrome/src/message-info.js Formats timestamps and generation metrics.
src/chrome/src/ui/sidepanel.js Renders, toggles, and persists message info.
src/chrome/styles/sidepanel.css Styles interactive messages and info pills.
src/chrome/src/ui/locales/ar.js Adds message-info fallback strings.
src/chrome/src/ui/locales/bn.js Adds message-info fallback strings.
src/chrome/src/ui/locales/de.js Adds message-info fallback strings.
src/chrome/src/ui/locales/en.js Adds English message-info strings.
src/chrome/src/ui/locales/es.js Adds message-info fallback strings.
src/chrome/src/ui/locales/fa.js Adds message-info fallback strings.
src/chrome/src/ui/locales/fr.js Adds message-info fallback strings.
src/chrome/src/ui/locales/he.js Adds message-info fallback strings.
src/chrome/src/ui/locales/hi.js Adds message-info fallback strings.
src/chrome/src/ui/locales/id.js Adds message-info fallback strings.
src/chrome/src/ui/locales/ja.js Adds message-info fallback strings.
src/chrome/src/ui/locales/ko.js Adds message-info fallback strings.
src/chrome/src/ui/locales/ms.js Adds message-info fallback strings.
src/chrome/src/ui/locales/nl.js Adds message-info fallback strings.
src/chrome/src/ui/locales/pl.js Adds message-info fallback strings.
src/chrome/src/ui/locales/pt.js Adds message-info fallback strings.
src/chrome/src/ui/locales/ru.js Adds message-info fallback strings.
src/chrome/src/ui/locales/th.js Adds message-info fallback strings.
src/chrome/src/ui/locales/tl.js Adds message-info fallback strings.
src/chrome/src/ui/locales/tr.js Adds message-info fallback strings.
src/chrome/src/ui/locales/uk.js Adds message-info fallback strings.
src/chrome/src/ui/locales/vi.js Adds message-info fallback strings.
src/chrome/src/ui/locales/zh.js Adds message-info fallback strings.
src/firefox/src/agent/agent.js Mirrors completion metric publishing.
src/firefox/src/message-info.js Mirrors message-info formatting.
src/firefox/src/ui/sidepanel.js Mirrors message-info UI and persistence.
src/firefox/styles/sidepanel.css Mirrors message-info styling.
src/firefox/src/ui/locales/ar.js Adds message-info fallback strings.
src/firefox/src/ui/locales/bn.js Adds message-info fallback strings.
src/firefox/src/ui/locales/de.js Adds message-info fallback strings.
src/firefox/src/ui/locales/en.js Adds English message-info strings.
src/firefox/src/ui/locales/es.js Adds message-info fallback strings.
src/firefox/src/ui/locales/fa.js Adds message-info fallback strings.
src/firefox/src/ui/locales/fr.js Adds message-info fallback strings.
src/firefox/src/ui/locales/he.js Adds message-info fallback strings.
src/firefox/src/ui/locales/hi.js Adds message-info fallback strings.
src/firefox/src/ui/locales/id.js Adds message-info fallback strings.
src/firefox/src/ui/locales/ja.js Adds message-info fallback strings.
src/firefox/src/ui/locales/ko.js Adds message-info fallback strings.
src/firefox/src/ui/locales/ms.js Adds message-info fallback strings.
src/firefox/src/ui/locales/nl.js Adds message-info fallback strings.
src/firefox/src/ui/locales/pl.js Adds message-info fallback strings.
src/firefox/src/ui/locales/pt.js Adds message-info fallback strings.
src/firefox/src/ui/locales/ru.js Adds message-info fallback strings.
src/firefox/src/ui/locales/th.js Adds message-info fallback strings.
src/firefox/src/ui/locales/tl.js Adds message-info fallback strings.
src/firefox/src/ui/locales/tr.js Adds message-info fallback strings.
src/firefox/src/ui/locales/uk.js Adds message-info fallback strings.
src/firefox/src/ui/locales/vi.js Adds message-info fallback strings.
src/firefox/src/ui/locales/zh.js Adds message-info fallback strings.
test/run.js Tests formatting, aggregation, and mirrored wiring.
Files not reviewed (5)
  • src/chrome/src/ui/locales/bn.js: Generated file
  • src/chrome/src/ui/locales/hi.js: Generated file
  • src/chrome/src/ui/locales/ru.js: Generated file
  • src/chrome/src/ui/locales/th.js: Generated file
  • src/chrome/src/ui/locales/uk.js: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/firefox/src/agent/agent.js

const chatMainTurn = async (chatMessages, chatOptions, requestContext) => {
const startedAt = Date.now();
const result = await chatMainTurnRaw(chatMessages, chatOptions, requestContext);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aafa249. The collector in _chatStreamWithCostAllowance now preserves the terminal reason from the provider done chunk (and its raw payload when present) and passes it into the result, so aggregateMessageCompletion can read result.finishReason instead of always receiving an empty reason on the primary streaming Ask path. The streaming providers that actually observe a terminal reason (OpenAI Chat Completions choice.finish_reason, Anthropic message_delta.delta.stop_reason, llama.cpp / Azure choice.finish_reason, Bedrock stopReason) now attach it to their done chunk. The Responses API has no per-generation stop reason, and the aggregation test already treats lifecycle status as not-a-reason, so no value is fabricated there. Verified: node test/run.js 1755 passed.

break;

case 'run_complete':
setMessageCreatedAt(eventAssistantEl || currentAssistantEl, data?.endedAt, { replace: true });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed fixed in 5b116dc: the synthesized run_complete in applyActiveRunState now includes data.endedAt: runUi.endedAt, so terminal restore uses the snapshot completion time instead of falling back to request-start/panel-open. The new behavioral test asserts the replaced sent time is retained across restore.

break;

case 'run_complete':
setMessageCreatedAt(eventAssistantEl || currentAssistantEl, data?.endedAt, { replace: true });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mirrored in 5b116dc on the Firefox sidepanel; covered by the same behavioral restore assertion in the new test.

Comment thread src/chrome/src/ui/sidepanel.js Outdated
Comment on lines +10504 to +10506
msgEl.tabIndex = 0;
msgEl.title = t('sp.message_info.hint');
msgEl.setAttribute('aria-expanded', String(msgEl.classList.contains('message-info-open')));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aafa249 with a semantic toggle button, per your suggestion:

  • Each message with a timestamp now gets a real <button type="button" class="message-info-toggle"> as a sibling of the info row (outside the bubble, so no nesting inside the message links/buttons), carrying aria-expanded and aria-controls pointing at a stable row id (message-info-<n>).
  • The message div no longer receives tabIndex = 0, aria-expanded, or a keydown handler; keyboard users activate the toggle button directly.
  • Bubble-click delegation is retained as a mouse convenience, and clicks on the button are excluded from it via the existing messageInfoClickIsInteractive guard.
  • refreshOpenMessageInfoRows refreshes the button label instead of the div title; restored messages re-create/reuse the button through messageInfoToggleButton.

One deliberate design note vs. a role="button" on the bubble: a real <button> gives native Enter/Space handling, focus management, and correct AT announcements, which a div with aria-expanded alone cannot — this matches your recommendation.

Comment thread src/firefox/src/ui/sidepanel.js Outdated
Comment on lines +10188 to +10190
msgEl.tabIndex = 0;
msgEl.title = t('sp.message_info.hint');
msgEl.setAttribute('aria-expanded', String(msgEl.classList.contains('message-info-open')));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mirrored in aafa249 — the Firefox sidepanel implements the same semantic toggle button (aria-controls/aria-expanded on a real button, stable row id, no tabIndex/aria-expanded/keydown on the bubble div, bubble-click delegation kept). Both builds covered by the new behavioral test.

Comment thread test/run.js
}
});

test('sidepanels reveal persisted message info while verbose gates completion details', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in aafa249 with a behavioral DOM test (message info toggles behaviorally through a semantic button, terminal replay, and restore in both builds). It extracts the real message-info functions from each sidepanel via vm and drives them with a small fake DOM, exercising: toggle via bubble click and via the semantic button, aria-expanded/aria-controls sync, applyMessageCompletion → dataset round-trip, terminal replay (run_complete endedAt replacing the sent time via setMessageCreatedAt(..., { replace: true })), and a restore round-trip that rebuilds a message from persisted datasets + the open class and asserts the completion timestamp and metrics are retained. The repo test runner is deliberately dependency-free ("pure Node, no framework"), so this follows the same vm-extraction pattern as the existing /print behavioral tests instead of adding jsdom.

…estore

Terminal restore in applyActiveRunState synthesized a run_complete event
without data.endedAt, so restored assistant messages fell back to the
request-start timestamp instead of the actual completion time. Mirror the
same fix in both Chrome and Firefox builds.
…antic info toggle

Address the remaining Copilot review comments on PR webbrain-one#2802:

- Stream collectors now forward the terminal finish_reason/stop_reason
  observed by providers (OpenAI Chat Completions, Anthropic, llama.cpp,
  Azure OpenAI, Bedrock) through the done chunk into aggregate results,
  so the verbose stop-reason pill appears in the primary Ask path.
- Replace the focusable message div (tabIndex + aria-expanded on a bare
  div) with a real toggle button carrying aria-controls/aria-expanded
  and a stable row id, while keeping bubble-click delegation.
- Add a behavioral DOM test that exercises toggle, persist/restore, and
  terminal replay for both builds.
@alectimison-maker

Copy link
Copy Markdown
Contributor Author

Summary — all four Copilot review items are now addressed:

  1. Streamed finish_reason propagation (agent.js collectors, Chrome + Firefox): the collector preserves the terminal reason from the provider done chunk (plus raw when present) and forwards it into aggregateMessageCompletion, so the stop-reason pill appears on the primary streaming Ask path. Providers that observe a real terminal reason now attach it to their done chunk (OpenAI Chat Completions finish_reason, Anthropic stop_reason, llama.cpp / Azure finish_reason, Bedrock stopReason). The Responses API has no per-generation stop reason and none is fabricated (the aggregation already treats lifecycle status as not-a-reason).
  2. endedAt through synthesized run_complete (5b116dc): restored terminal messages use the snapshot completion time.
  3. A11y: semantic toggle button (aafa249): replaced the focusable div (tabIndex + aria-expanded on a bare div) with a real <button class="message-info-toggle"> carrying aria-controls/aria-expanded against a stable row id, kept bubble-click delegation, and removed the div-level keydown/title/aria-expanded. See the thread replies for the design rationale.
  4. Behavioral test (aafa249): new DOM-level test extracts the real sidepanel functions via vm and drives a fake DOM through toggle (bubble + button), applyMessageCompletion, terminal replay, and persist/restore for both builds — asserting restored timestamp and metrics are retained.

Verification: node test/run.js — 1755 passed, 0 failed; toolbar-guard 33 passed; injection corpus 60/60.

@esokullu
esokullu merged commit 6220ecb into webbrain-one:main Aug 16, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

when you click on message, they should show info pills under like this

3 participants