Display a Goal badge in the user message
Problem
A mockup puts a "Goal" badge on the chat bubble that shows the user's own /goal
message. Until recently that bubble was rendered by a closed, hardcoded renderer in
GitLab's chat code, with no way to inject anything into it.
Proposal
duo-ui duo-ui!695 (merged) (merged) now routes
user messages through duo-ui's message registry, so a messageWidgets descriptor can
claim them. duo-ui duo-ui!696 (merged) (open,
needs retarget to main) adds the header/content slots this badge needs, and is
the prerequisite this issue is blocked on.
- Contribute a
messageWidgetsentry that wraps duo-ui'sUserMessageand fills itsheaderslot with the badge, instead of rebuilding the bubble. - Mark goal messages with
message_sub_type: 'goal'from amessageTransformer— or reuse the backend's value if it already sets one. - Strip the
/goaltoken from the rendered body; the badge replaces it visually. - Cover both the optimistic local message and messages replayed from
checkpoint/history, and mirror the wiring in
duo_agentic_chat_state_manager_light.vue.
Technical notes
The epic's UX note names the work-item-objective icon. The usage example in duo-ui
merge request duo-ui!696 (merged) uses
issue-type-objective instead. Both exist in @gitlab/svgs. Use the epic's
work-item-objective and flag the mismatch for UX to confirm.
Acceptance criteria
Prerequisites
- duo-ui duo-ui!696 (merged) retargeted to
main, merged, and released; then bump@gitlab/duo-uiinpackage.jsonpast that release.!254241(the open bump to^16.3.0) only carries duo-ui!695 (merged). - duo-ui duo-ui!695 (merged) having merged is
what makes this possible at all: user messages now route through duo-ui's message
registry, so a
messageWidgetsdescriptor can claim them.
Work
- The
goalplugin contributes amessageWidgetsentry whosematchMessageclaims the goal-bearing user message and whosecomponentis a thin GitLab-side wrapper around duo-ui's newly exportedUserMessage, filling itsheaderslot with the badge. Do not rebuild the bubble — theheaderslot exists so the markdown pipeline, bubble styling, attribution label, and context references stay with duo-ui. - Mark the message with
message_sub_type: 'goal'(the field the duo-ui MR's example matches on) from amessageTransformerin the same plugin, followingplugins/compact/transformers/drop_noop_compaction.js. Transformers run intransformedMessagesand must be pure — keep the detection there rather than inmatchMessage, which duo-ui calls for every renderer against every message. If the backend already setsmessage_sub_type, prefer that and drop the transformer. - The
/goaltoken is not rendered in the message body — the badge replaces it. - Correct for the optimistic local message (
addDuoChatMessage({ content, role: 'user' })) and for messages replayed from checkpoint/history (transformChatMessages,normalizeDuoMessages), so the badge survives a reload. -
duo_agentic_chat_state_manager_light.vuegets the same treatment — it keeps its own copy of the log and its ownmessageRenderers. - Add Jest coverage for the transformer, the widget descriptor, and the wrapper.
References
- Epic: gitlab-org#23107
- duo-ui MR (merged): duo-ui!695 (merged)
- duo-ui MR (open, needs retarget to
main): duo-ui!696 (merged) - Renovate dependency bump: !254241 (merged)
Part of epic &23107.