fix(view-context): a corner mark on the bubble, not a row under it
Nightly Build / build (push) Successful in 9s
Nightly Build / build (push) Successful in 9s
The sent-message proof was a collapsed <details> under the user bubble: a row of height in every bubble that carried view context, for something that is evidence about the message rather than part of it. It also rendered a blank line, since the bubble is white-space: pre-wrap and the template left a newline before the element. Now a faint eye in the bubble's bottom-right corner, revealing the pairs on hover (on focus for keyboard and touch). Deliberately not expandable, so scrolling back through a conversation cannot grow a second layout, and the popover opens downwards rather than over the message it belongs to. The i18n key stays alive as the mark's aria-label; docs and changelog wording updated to match.
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@ release PR may merge — and a section is closed at the commit that bumps it.
|
||||
assistant reading through it. Like an attachment, what the eye sends goes to the AI
|
||||
provider together with your message — hover it (or tap it) to read exactly what would
|
||||
go out, click it to stop sharing; the choice is remembered on this device, and every
|
||||
message shows a chip with what it carried. Very long highlights are trimmed, with a
|
||||
sent message keeps a faint eye in its corner that shows, on hover, what it carried. Very long highlights are trimmed, with a
|
||||
note saying how much was left out — the assistant can still read the whole file itself.
|
||||
On by default.
|
||||
- Several conversations per source: open extra chats with `+`, and the tab bar you left
|
||||
|
||||
@@ -12,7 +12,7 @@ Exactly one snapshot per message, covering whatever applies at that moment:
|
||||
- **A highlighted passage** — if the user selected text in the viewer, the selected text itself, with its line numbers when they are looking at the source (a plain-text file, or the editor view of a Markdown file).
|
||||
- **Which thing a detail page is about** — which project (and which of its tabs), which member, connector, plugin, conversation, tool call or LLM request; also the active section in Tasks or Models, the open agent in Background agents, and a search typed in the Marketplace.
|
||||
|
||||
Hover the eye (or tap it, on a touch screen) to read exactly what would be sent with the next message. Every sent message shows a small chip with what it carried, which can be opened to read the actual values.
|
||||
Hover the eye (or tap it, on a touch screen) to read exactly what would be sent with the next message. Every sent message keeps a small eye in its corner; hovering it (or tapping it, on a touch screen) shows the actual values that message carried.
|
||||
|
||||
## Control and privacy
|
||||
|
||||
|
||||
@@ -591,18 +591,22 @@ export function renderViewContextPill(host) {
|
||||
* The proof, in the sent bubble: what this message actually carried. Rendered
|
||||
* from the server's echo (and, after a reload, from the REST history), so it
|
||||
* shows the sanitized pairs the model was given — never the browser's intent.
|
||||
*
|
||||
* A mark in the bubble's corner, not a row under the message: this is evidence
|
||||
* about a message, not part of it, and a collapsed row still cost a line of
|
||||
* height in every bubble that carried one. Deliberately **not** expandable —
|
||||
* the pairs show on hover (on focus for keyboard and touch) and there is no
|
||||
* state to leave open, so a conversation scrolled back through never grows a
|
||||
* second layout.
|
||||
*
|
||||
* Written on one line on purpose: the bubble renders `white-space: pre-wrap`, so
|
||||
* the newline and indent a formatted template would leave before the element are
|
||||
* a text node the bubble actually shows — a blank line under the message.
|
||||
*/
|
||||
function renderViewContextChip(host, msg) {
|
||||
function renderViewContextMark(host, msg) {
|
||||
const items = msg.view_context;
|
||||
if (!items?.length) return nothing;
|
||||
return html`
|
||||
<details class="view-ctx-chip">
|
||||
<summary>
|
||||
<i class="bi bi-eye"></i>
|
||||
<span>${t('chat.view_context.chip', { n: items.length })}</span>
|
||||
</summary>
|
||||
${renderViewContextItems(items)}
|
||||
</details>`;
|
||||
return html`<div class="view-ctx-mark" tabindex="0" aria-label=${t('chat.view_context.chip', { n: items.length })}><i class="bi bi-eye"></i><div class="view-ctx-pop">${renderViewContextItems(items)}</div></div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -625,7 +629,7 @@ export function renderMsg(host, msg) {
|
||||
try {
|
||||
switch (msg.kind) {
|
||||
case 'user':
|
||||
return html`<div class="copilot-msg user ${msg.failed ? 'copilot-msg--failed' : ''}" style="white-space:pre-wrap">${msg.failed ? failedBadge() : nothing}${msg.content}${renderAttachmentChips(host, msg.attachments)}${renderViewContextChip(host, msg)}</div>`;
|
||||
return html`<div class="copilot-msg user ${msg.view_context?.length ? 'copilot-msg--view-ctx' : ''} ${msg.failed ? 'copilot-msg--failed' : ''}" style="white-space:pre-wrap">${msg.failed ? failedBadge() : nothing}${msg.content}${renderAttachmentChips(host, msg.attachments)}${renderViewContextMark(host, msg)}</div>`;
|
||||
case 'thinking':
|
||||
return html`
|
||||
<div class="copilot-msg assistant copilot-markdown ${msg.failed ? 'copilot-msg--failed' : ''}">
|
||||
|
||||
@@ -889,33 +889,72 @@
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
/* The chip inside a sent user bubble: collapsed by default, same muted weight as
|
||||
the reasoning block — it is evidence, not content. */
|
||||
.view-ctx-chip {
|
||||
margin-top: 0.4rem;
|
||||
font-size: 0.75rem;
|
||||
white-space: normal;
|
||||
/* The mark in a sent user bubble's corner: it is evidence about the message, not
|
||||
part of it, so it costs the bubble a corner rather than a row of its own. Kept
|
||||
faint on purpose — it should be findable when looked for and invisible when
|
||||
not. Hover (focus, on touch and for the keyboard) reveals the pairs; there is
|
||||
no expanded state, by design. */
|
||||
.copilot-msg.user.copilot-msg--view-ctx {
|
||||
position: relative;
|
||||
/* Room for the mark, so a last line can never run under it. */
|
||||
padding-bottom: 1.35rem;
|
||||
}
|
||||
|
||||
.view-ctx-chip > summary {
|
||||
.view-ctx-mark {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
bottom: 0.25rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.2rem 0.45rem;
|
||||
border: 1px solid var(--toolbar-border);
|
||||
border-radius: 0.5rem;
|
||||
color: var(--placeholder-color);
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
line-height: 1.2;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1;
|
||||
color: inherit;
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
transition: opacity 0.12s;
|
||||
}
|
||||
|
||||
.view-ctx-chip > summary::-webkit-details-marker { display: none; }
|
||||
.view-ctx-chip > summary:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.view-ctx-mark:hover,
|
||||
.view-ctx-mark:focus { opacity: 0.9; outline: none; }
|
||||
.view-ctx-mark:focus-visible { opacity: 0.9; outline: 2px solid currentColor; outline-offset: 2px; border-radius: 0.2rem; }
|
||||
|
||||
.view-ctx-chip .view-ctx-items {
|
||||
margin-top: 0.4rem;
|
||||
padding-left: 0.15rem;
|
||||
/* Below the bubble, right-aligned with it: above would cover the message the
|
||||
mark belongs to. */
|
||||
.view-ctx-pop {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
display: block;
|
||||
width: max-content;
|
||||
max-width: min(26rem, 70vw);
|
||||
max-height: 16rem;
|
||||
overflow-y: auto;
|
||||
padding: 0.5rem 0.65rem;
|
||||
border: 1px solid var(--toolbar-border);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--msg-assistant-bg);
|
||||
color: var(--msg-assistant-text);
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
|
||||
font-size: 0.75rem;
|
||||
/* The bubble is pre-wrap; the popover formats its own values. */
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.12s;
|
||||
}
|
||||
|
||||
/* Interactive only while shown: `:hover` applies to ancestors of the hovered
|
||||
element, so letting the pointer onto the popover keeps the mark hovered — which
|
||||
is what makes a long, scrollable value readable. Hidden, it must not eat a
|
||||
click on the bubble underneath. */
|
||||
.view-ctx-mark:hover .view-ctx-pop,
|
||||
.view-ctx-mark:focus .view-ctx-pop {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* ── Tool card "view details" (eye) ────────────────────────────────────────── */
|
||||
|
||||
Reference in New Issue
Block a user