Skip to content

Show message numbers (ids) in the UI so humans can follow "msg #NNN" references #70

@maxlamagna

Description

@maxlamagna

Summary

Agents reference each other's messages by the message id — the integer that store.py assigns to every message and that the MCP bridge hands back to agents (e.g. Sent to job #12 (msg_id=437), mcp_bridge.py). So an agent will say things like "see msg 437" or "re: message 437, …". But the web UI never renders that id, so as a human reading the chat I have no way to tell which message is "437." The number exists end-to-end on the agent side and is invisible on the human side.

It's already in the DOM, too — every bubble is built with el.dataset.id = msg.id in appendMessage (static/chat.js) — the UI just doesn't paint it.

Proposal

Three parts. The first two are small and self-contained (a draft PR is attached — see below); the third is a follow-up idea I'd love your read on before building.

1. Render #id in the chat bubble header.
Only on regular chat messages — the row that already shows sender / role / time (bubble-header in appendMessage). Join/leave, summaries, and proposals render differently and don't need it. The id shown is exactly msg.id, i.e. the same number agents cite, so they line up by construction.

2. A Settings toggle to show/hide it.
A "Msg numbers" control in the cog/settings panel (#settings-bar), next to Contrast — defaulting to Hide so it's opt-in and changes nothing for existing users. It mirrors the existing Contrast setting exactly: a body-class toggle (show-msg-numbers), persisted server-side through the update_settings round-trip in room_settings. The badge is always rendered in the DOM but hidden by CSS unless the body class is present, so toggling is pure presentation.

3. (Follow-up / for discussion) Make a referenced number a link that jumps to the message.
When an agent writes "msg 437," it'd be lovely to click it and scroll to that message. The infrastructure is already there:

  • scrollToMessage(id) (static/chat.js) already scrolls to a message and flashes a highlight — it's used today by reply-quotes and the pins panel.
  • The text-render pipeline already does this kind of inline styling via styleHashtags(renderMarkdown(...)). A linkifyMsgRefs() step in the same place would wrap matches in a clickable span calling scrollToMessage.

The catch — and the reason I'm raising it as a discussion rather than just shipping it — is that not every number is a message reference. "takes 5 minutes" or "port 8080" must not become links. Some options, roughly least → most magic:

  • Require an explicit grammar. Only linkify a defined token, e.g. msg 437, msg #437, or message 437 (regex like \bmsg(?:sage)?\s*#?\s*(\d+)\b). Prose numbers are left alone. Pairs well with documenting that convention in the README / agent system prompt so agents cite messages consistently.
  • Repurpose #NNN. A pure-numeric hashtag like #437 is currently not styled (styleHashtags requires a leading letter), so it's free to mean "message 437." Compact, but #437 is also a natural way to write other things.
  • Validate against the DOM (recommended as a guard on whichever grammar). Only linkify when a message with that id actually exists on the page (document.querySelector('.message[data-id="437"]')). Anything that doesn't resolve stays plain text. This kills almost all false positives regardless of grammar.

My instinct: explicit grammar + DOM-existence guard. Happy to follow your preference, and happy to split this into its own PR once the approach is settled — it also lightly touches the agent-facing convention, which is your call.

Implementation notes

  • Setting persistence mirrors contrast: default in room_settings (app.py), validated in the update_settings handler, broadcast back via broadcast_settings (whole room_settings dict, so it round-trips automatically), applied in applySettings / saveSettings / setupSettingsKeys (static/chat.js).
  • Default off = non-breaking; no existing view changes until a user opts in. Easy to flip the default to on if you'd rather it be visible out of the box.
  • Persistence choice is server-side to match the other settings in that panel; if you'd prefer a client-only (localStorage) toggle for a display-only pref, that's an easy swap.

Draft PR

I've prepared a draft PR for parts 1 + 2 (≈36 lines across app.py, static/chat.js, static/index.html, static/style.css) — happy to open it against main, or to adjust default/persistence first based on your preference. Part 3 I'd hold pending your read on the approach above.

Tested locally on v0.4.0: python -m py_compile app.py and node --check static/chat.js both pass; toggling Show/Hide flips the badges live and the setting survives a reload.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions