feat: render selected context as quote cards#4380
Conversation
cd91f2d to
0e017c7
Compare
0e017c7 to
45718cc
Compare
|
| Filename | Overview |
|---|---|
| static/messages.js | Core composer logic rewritten: chips become quote cards built entirely with DOM APIs; _composerTextWithPendingSelections and _clearComposerAfterQueuedSelectionSend fix the queued-send path; _flushSelectionBlocksToComposer correctly placed after the _sendInProgress guard. |
| static/ui.js | _stashUserSelectedContextBlocks + _sentSelectionContextBlockHtml added to _renderUserFencedBlocks; stash tokens survive esc() correctly; both label and quote content pass through esc() before insertion into HTML. |
| static/style.css | Chips replaced with full-width card styles; composer stack is capped with max-height/overflow-y; touch targets meet 44px minimum via coarse-pointer media query; sent-thread figure styles added. |
| static/i18n.js | Three new i18n keys (rename hint, rename aria, remove label) added consistently across all 13 locales. |
| tests/test_selected_context_user_render_runtime.py | New runtime test file that executes the renderer logic in a real Node.js subprocess; covers XSS escaping, edge-case labels, and code-fence protection. |
| tests/test_issue2481_selected_text_reply.py | Existing regression tests updated and expanded: covers card DOM structure, focus restoration, aria labels, queue-path helpers, and sent-thread renderer contract. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User selects text\n'Reply with selection'] --> B[_addNamedContextBlock]
B --> C[_pendingSelections array]
C --> D[_renderSelectionChips\nbuilds quote cards via DOM APIs]
D --> E[Composer: card stack\naccent rail + label button + remove]
E --> F{Send clicked}
F -->|_sendInProgress = true\nqueue path| G[_composerTextWithPendingSelections\ncaptures text + blocks]
G --> H[queueSessionMessage with full text]
H --> I[_clearComposerAfterQueuedSelectionSend\nclears textarea + _pendingSelections]
F -->|normal path| J[check empty: text + files + _pendingSelections]
J --> K[_flushSelectionBlocksToComposer\nwrites **Name:**\\n> quote to textarea]
K --> L[text = $msg.value.trim\nsend to model]
L --> M[_clearPendingSelections]
L --> N[Thread: renderUserMessage\n_renderUserFencedBlocks]
N --> O[_stashUserSelectedContextBlocks\nstash **Name:**\\n> quote to UC token]
O --> P[esc remaining text\nnewlines to br]
P --> Q[restore UC tokens\nfigure.sent-selection-context\nfigcaption + blockquote via esc]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[User selects text\n'Reply with selection'] --> B[_addNamedContextBlock]
B --> C[_pendingSelections array]
C --> D[_renderSelectionChips\nbuilds quote cards via DOM APIs]
D --> E[Composer: card stack\naccent rail + label button + remove]
E --> F{Send clicked}
F -->|_sendInProgress = true\nqueue path| G[_composerTextWithPendingSelections\ncaptures text + blocks]
G --> H[queueSessionMessage with full text]
H --> I[_clearComposerAfterQueuedSelectionSend\nclears textarea + _pendingSelections]
F -->|normal path| J[check empty: text + files + _pendingSelections]
J --> K[_flushSelectionBlocksToComposer\nwrites **Name:**\\n> quote to textarea]
K --> L[text = $msg.value.trim\nsend to model]
L --> M[_clearPendingSelections]
L --> N[Thread: renderUserMessage\n_renderUserFencedBlocks]
N --> O[_stashUserSelectedContextBlocks\nstash **Name:**\\n> quote to UC token]
O --> P[esc remaining text\nnewlines to br]
P --> Q[restore UC tokens\nfigure.sent-selection-context\nfigcaption + blockquote via esc]
Reviews (2): Last reviewed commit: "feat: render selected context as quote c..." | Re-trigger Greptile
45718cc to
e63929c
Compare
|
Updated in e63929c: pending selections are now included in the concurrent queued-send path via _composerTextWithPendingSelections(), and the queued path clears the composer/cards consistently after queueing. Also changed the pending-card tooltip to expose the full selected text instead of the clipped preview. Focused verification: node --check static/messages.js static/ui.js and ./scripts/test.sh tests/test_issue2481_selected_text_reply.py tests/test_selected_context_user_render_runtime.py tests/test_issue2543_named_context_session_switch.py -q => 13 passed. |
|
Pulled Queue-path fix — correctThe real bug the prior round left open was that the concurrent ( if (_sendInProgress) {
const _text=_composerTextWithPendingSelections().trim();
...
queueSessionMessage(_targetSid,{text:_text, ...});
_clearComposerAfterQueuedSelectionSend();
Sent-thread renderer — XSS-safe
return `<figure class="sent-selection-context" data-selected-context="1"><figcaption ...>${esc(safeLabel)}</figcaption><blockquote ...>${esc(safeQuote)}</blockquote></figure>`;and the One heuristic note (low severity, your call)
const labelMatch=lines[i].match(/^\*\*([^\n]{1,200}):\*\*\s*$/);
...
while(j<lines.length&&/^>/.test(lines[j])){ quoteLines.push(...); j++; }There's no provenance sentinel distinguishing "this came from Reply-with-selection" from "the user hand-typed this exact shape." So a user who manually types i18n looks complete (all three new keys present 13×, one per locale). Nice work on the keyboard a11y (Enter/Space/F2 rename + |
Thinking Path
Reply with selectionshould feel like composed reply context, not raw Markdown dumped into the textarea or thread.**Name:**/> quotesyntax literally.What Changed
textContent, avoiding user-content interpolation throughinnerHTML.<figure class="sent-selection-context"><figcaption>for the context label<blockquote>for the selected passageWhy It Matters
Selected text context is easier to read, rename, remove, and trust before sending. After sending, the thread remains readable too: the user’s actual reply stays normal, and the selected context appears as a clean referenced block instead of raw Markdown syntax.
Verification
node --check static/ui.jsnode --check static/messages.jsnode --check static/i18n.jsgit diff --check origin/master...HEAD./scripts/test.sh tests/test_issue2481_selected_text_reply.py tests/test_selected_context_user_render_runtime.py tests/test_issue2543_named_context_session_switch.py -q→12 passedfigure/blockquoteblock;**Name:**or> quotesyntax for generated selected context.Risks / Follow-ups
**Name:**+> quoteshape in user bubbles. User content remains escaped, but manually typed text with that exact shape will also get the same visual treatment.Contract Routing
Task type: UI/UX behavior improvement.
Touched areas:
static/messages.jsselected-text / named-context composer behaviorstatic/ui.jssent user-message selected-context renderingstatic/style.csscomposer and sent-thread selected-context stylingstatic/i18n.jsselected-context labelsCHANGELOG.mdRelevant public docs:
AGENTS.mdCONTRIBUTING.mddocs/CONTRACTS.mddocs/UIUX-GUIDE.mdScope boundaries:
Disclosure
AI-assisted implementation and review support was used. No secrets or private runtime state are required by this change.