| null>(null);
const blurCommitFrameRef = useRef<(() => void) | null>(null);
+ const justEnteredEditRef = useRef(false);
+ const hasBeenFocusedRef = useRef(false);
const {
state: autosaveState,
markDirty,
@@ -106,12 +110,27 @@ export function InlineEditor({
}, [editing, autoSize]);
useEffect(() => {
- if (!editing || !multiline) return;
- const frame = requestAnimationFrame(() => {
- markdownRef.current?.focus();
- });
- return () => cancelAnimationFrame(frame);
- }, [editing, multiline]);
+ if (!multilineEditing || !multiline) return;
+ if (!justEnteredEditRef.current) return;
+ justEnteredEditRef.current = false;
+ markdownRef.current?.focus();
+ }, [multilineEditing, multiline]);
+
+ // Once the editor has been focused at least once, it's blurred, and any
+ // autosave has settled, swap back to the MarkdownBody preview so inline
+ // issue refs render with status + quicklook.
+ useEffect(() => {
+ if (multilineFocused) {
+ hasBeenFocusedRef.current = true;
+ return;
+ }
+ if (!multiline || !multilineEditing) return;
+ if (!hasBeenFocusedRef.current) return;
+ if (autosaveState !== "idle") return;
+ hasBeenFocusedRef.current = false;
+ setMultilineEditing(false);
+ }, [multiline, multilineEditing, multilineFocused, autosaveState]);
+
const commit = useCallback(async (nextValue = draft) => {
const valueToSave = nextValue.trim();
@@ -176,6 +195,8 @@ export function InlineEditor({
setDraft(value);
if (multiline) {
setMultilineFocused(false);
+ setMultilineEditing(false);
+ hasBeenFocusedRef.current = false;
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
@@ -212,6 +233,36 @@ export function InlineEditor({
}, [autosaveState, commit, draft, markDirty, multiline, multilineFocused, nullable, reset, runSave, value]);
if (multiline) {
+ const hasValue = Boolean(value.trim());
+ const showEditor = multilineEditing || multilineFocused || !hasValue;
+
+ if (!showEditor) {
+ const enterEditMode = () => {
+ justEnteredEditRef.current = true;
+ setMultilineEditing(true);
+ };
+ return (
+ {
+ if (event.defaultPrevented) return;
+ const target = event.target as HTMLElement | null;
+ if (target && target.closest("a,button,[data-mention-kind],[data-radix-popper-content-wrapper]")) {
+ return;
+ }
+ enterEditMode();
+ }}
+ onDragEnter={() => enterEditMode()}
+ role="textbox"
+ aria-label={placeholder}
+ >
+
+ {value}
+
+
+ );
+ }
+
return (
{
+ onFocusCapture={(event) => {
+ // Ignore focus events where the active element isn't actually inside
+ // the wrapper (React 19 can emit a synthetic focus after a blur).
+ const active = document.activeElement;
+ if (!(active instanceof Node) || !event.currentTarget.contains(active)) return;
cancelPendingBlurCommit();
setMultilineFocused(true);
}}
diff --git a/ui/src/index.css b/ui/src/index.css
index 95643466a50..12077489d8b 100644
--- a/ui/src/index.css
+++ b/ui/src/index.css
@@ -448,11 +448,23 @@
font-size: 0.75rem;
line-height: 1.3;
text-decoration: none;
- vertical-align: middle;
+ vertical-align: baseline;
white-space: nowrap;
user-select: none;
}
+/* Strip the MDXEditor's default inline-code styling from the text inside chips
+ (the link label otherwise picks up a monospace font + gray tint). */
+.paperclip-mdxeditor-content a.paperclip-mention-chip,
+.paperclip-mdxeditor-content a.paperclip-mention-chip code,
+.paperclip-mdxeditor-content a.paperclip-project-mention-chip,
+.paperclip-mdxeditor-content a.paperclip-project-mention-chip code {
+ font-family: inherit;
+ background: none;
+ color: inherit;
+ padding: 0;
+}
+
.paperclip-mdxeditor-content a.paperclip-mention-chip::before,
a.paperclip-mention-chip::before {
content: "";
@@ -832,9 +844,11 @@ a.paperclip-mention-chip[data-mention-kind="agent"]::before {
background: transparent;
}
-/* Project mention chips rendered inside MarkdownBody */
+/* Mention chips rendered inline in prose (MarkdownBody or inline anchors) */
a.paperclip-mention-chip,
-a.paperclip-project-mention-chip {
+a.paperclip-project-mention-chip,
+span.paperclip-mention-chip,
+span.paperclip-project-mention-chip {
display: inline-flex;
align-items: center;
gap: 0.25rem;
@@ -845,10 +859,25 @@ a.paperclip-project-mention-chip {
font-size: 0.75rem;
line-height: 1.3;
text-decoration: none;
- vertical-align: middle;
+ /* Align the pill relative to the surrounding text baseline instead of its
+ x-height midpoint so it sits on the text line rather than floating above. */
+ vertical-align: baseline;
white-space: nowrap;
}
+/* When the identifier inside a chip is backtick-wrapped in markdown, strip the
+ inline-code monospace/gray styling so the pill label reads cleanly. */
+.paperclip-markdown a.paperclip-mention-chip code,
+.paperclip-markdown a.paperclip-project-mention-chip code,
+.paperclip-markdown span.paperclip-mention-chip code,
+.paperclip-markdown span.paperclip-project-mention-chip code {
+ font-family: inherit;
+ background: none;
+ color: inherit;
+ padding: 0;
+ font-size: inherit;
+}
+
/* Keep MDXEditor popups above app dialogs, even when they portal to . */
[class*="_popupContainer_"] {
z-index: 81 !important;