diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db90cf..9d96a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/view-context.md b/docs/view-context.md index 2eb3589..2c3ad6a 100644 --- a/docs/view-context.md +++ b/docs/view-context.md @@ -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 diff --git a/web/components/copilot-render.js b/web/components/copilot-render.js index 3292ff2..50642aa 100644 --- a/web/components/copilot-render.js +++ b/web/components/copilot-render.js @@ -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` -
- - - ${t('chat.view_context.chip', { n: items.length })} - - ${renderViewContextItems(items)} -
`; + return html`
${renderViewContextItems(items)}
`; } /** @@ -625,7 +629,7 @@ export function renderMsg(host, msg) { try { switch (msg.kind) { case 'user': - return html`
${msg.failed ? failedBadge() : nothing}${msg.content}${renderAttachmentChips(host, msg.attachments)}${renderViewContextChip(host, msg)}
`; + return html`
${msg.failed ? failedBadge() : nothing}${msg.content}${renderAttachmentChips(host, msg.attachments)}${renderViewContextMark(host, msg)}
`; case 'thinking': return html`
diff --git a/web/css/copilot-messages.css b/web/css/copilot-messages.css index c718d07..3f30d38 100644 --- a/web/css/copilot-messages.css +++ b/web/css/copilot-messages.css @@ -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) ────────────────────────────────────────── */