* fix(composer): keep IME-composition Enter out of the slash-skill catalog
`handlePromptTextareaKeyDown` called `handleSkillSuggestionKeyDown` with no
composition guard, unlike its two siblings: the inline-skill editor wraps the
same call in `isIMEComposing(...)` with a comment naming this exact hazard, and
`handlePromptHistoryKeyDown` checks it too. `PromptInputTextarea` calls the
prop's `onKeyDown` *before* its own `isIMEComposing` check, so the suggestion
handler won the event.
With an IME active and the `/` catalog open, pressing Enter to commit the
composition instead ran `event.preventDefault()` and applied the highlighted
suggestion — discarding the text still being composed and selecting a skill the
user never chose. The catalog opens from the live `textInput.value`, and React
`onChange` fires during composition, so a CJK user typing `/res` to reach a
skill hits it directly.
Guard the main composer's call site the same way the inline-skill editor does,
and pin it with a happy-dom regression test: `keyCode: 229` (the keydown a
browser reports for a key an active IME consumed) must leave both the catalog
and the draft untouched, while a plain Enter still selects the highlighted
skill.
Verified:
- new test `tests/unit/components/workspace/input-box-skill-suggestion-ime.dom.test.tsx`
fails on main (the IME case applies the skill) and passes here; the plain-Enter
control case passes on both
- `rstest run --pool threads tests/unit/components/workspace` -> 69 files / 448
tests passed
- `tsc --noEmit`, `eslint`, `prettier --check` clean on the touched files
* fix(composer): ignore the Enter that follows IME compositionend
Safari emits the confirming Enter after compositionend, with neither
isComposing nor keyCode 229. Drop that Enter in PromptInputTextarea for
a short window, before the skill catalog or prompt history can treat it
as a plain key.
---------
Co-authored-by: Shxiao101 <Shxiao101@users.noreply.github.com>
* fix(frontend): use code-aware stripLeakedSystemTags for all internal marker tags
Replace the ad-hoc /<\/?memory>/g in ClipboardSafeStreamdown with a
dedicated stripLeakedSystemTags() function in preprocess.ts that:
- Covers all INTERNAL_MARKER_TAGS (<memory>, <system-reminder>,
<current_date>, <uploaded_files>, <slash_skill_activation>) instead
of only <memory>
- Is code-aware: skips fenced code blocks (```) and indented code
blocks (4-space indent), so user-written meta-discussions about the
memory system are not silently stripped
- Handles opening tags, closing tags, self-closing tags, and tags with
attributes
- Adds 15 unit tests covering all the above cases
* fix(frontend): use marker-aware fence tracking in stripLeakedSystemTags
The boolean insideFence toggle incorrectly closes a fence on any line
matching 3+ backticks or tildes, regardless of the actual delimiter.
This causes tags inside a tilde-fenced block containing a backtick
sub-fence, or a 4-backtick block containing a 3-backtick sub-fence, to
be silently stripped.
Track the opening fence marker (character and run length) so that only
a matching marker with at least the same length closes the fence.
Adds 4 new test cases:
- Tilde fence with inner backtick fence
- 4-backtick fence with inner 3-backtick fence
- Shorter tilde closing inside longer tilde fence
- Tags stripped after real closing fence
* fix(frontend): fix TypeScript strict errors in fence marker tracking
- Use non-null assertion (!) on fenceMatch[1] (guaranteed by regex)
- Use charAt(0) instead of [0] to avoid string | undefined type
* fix(frontend): improve chat math rendering
* fix(frontend): refine math rendering stability
* fix(frontend): address review feedback on math preprocessing
- Fix escaped-backslash blind spot: consume \\\\ as a unit so \\( and
\\[ are not mis-interpreted as math delimiters when the backslash
itself is escaped.
- Thread inInlineCode state across lines so multi-line backtick code
spans are protected from delimiter conversion.
- Remove double math normalization: preprocessStreamdownMarkdown now
only handles Mermaid; math normalization lives solely in
ClipboardSafeStreamdown, preventing non-idempotent double passes.
- Wire parseIncompleteMarkdown to isLoading in MarkdownContent so
Streamdown's streaming-incomplete-math handling is actually active
during streaming.
* fix(frontend): address streamdown math review issues
* refactor(frontend): move streamdown preprocessing out of ai elements
* feat: persist AI turn duration in backend and UI
* chore: restore uv.lock to match main
* refactor(frontend): use Math.floor instead of Math.ceil for reasoning timer
* fix(frontend): cap deeply nested list indentation to prevent render crash
Deeply nested lists make marked's recursive list tokenizer overflow the
call stack during Streamdown's lexing useMemo, throwing an uncaught
"RangeError: Maximum call stack size exceeded" that replaces the chat
route with an error page (issue #3393); on larger stacks the same input
exhausts the heap, which the render error boundary cannot catch.
Mirror the existing capBlockquoteNesting guard with capListNesting, which
clamps leading whitespace to 200 columns (~100 nesting levels) only when
pathologically deep indentation is present, leaving normal content and
fenced code untouched. Wire both through capMarkdownNesting.
* fix(frontend): satisfy prettier format check in preprocess test
* fix(frontend): exempt indented code from list-indent cap (PR #3570 review)
* fix(frontend): keep capping all deep indentation outside fenced code
Revert the indented-code exemption from the PR #3570 review nit. Taken
literally the suggested guard (insideFence || INDENTED_CODE_RE.test(line))
no-ops capListNesting, because INDENTED_CODE_RE matches every line with
4+ leading spaces — i.e. exactly the deep-indent lines the cap targets.
A context-aware exemption (only treat 4+-space lines as code after a
blank line) instead reopens the crash: blank-separated deeply nested list
items get exempted and still blow up marked (verified: OOM at depth ~1.5k).
Unlike blockquotes (markers take <=3 leading spaces, so deep-quote lines
never look like indented code), list vs. indented-code indentation is
ambiguous line-by-line, so any exemption is exploitable. Keep capping all
deep indentation outside fenced code; the only cost is mild corruption of
a >200-column indented-code line, which never occurs in real content and
is strictly preferable to a render crash. Add a regression test locking
the blank-line case.
* fix(frontend): render user messages as plain text and cap blockquote nesting
User messages are typed or pasted plain text, not authored Markdown, but
they were rendered through the full Streamdown pipeline. Pasted source
files got fragmented (indented chunks become code blocks, paragraphs
collapse and lose indentation), "$...$" spans were KaTeX-ified, and a
message with thousands of nested ">" markers overflowed the call stack
in marked's recursive blockquote lexer, permanently crashing the thread
on every load.
Render human message content verbatim with pre-wrap instead, and cap
blockquote nesting at 100 levels at the Streamdown chokepoint so model
output cannot trigger the same recursion either.
Closes#3500
* fix(frontend): absorb marked lexer crashes with a render fallback boundary
Review found two gaps in the nesting cap: marked's list and blockquote
tokenizers are mutually recursive, so a list marker in front of the
quote chain ("- > > > ...") bypassed the blockquote-only regex and
still overflowed the stack; and the line-based rewrite was fence-blind,
silently truncating literal ">" runs inside code blocks.
Add an error boundary around Streamdown that renders the raw content as
plain pre-wrap text when rendering throws (retrying on the next content
change), keep the cap as a fast path for the dominant pure-">" case,
and make it skip fenced and indented code lines.
* fix(frontend): make Suggestion button opaque in dark mode
The outline Button variant applies dark:bg-input/30, leaving Suggestion
pills ~70% transparent in dark mode. Scrolled chat content bled through
the buttons, making suggestion text unreadable. Override with
dark:bg-background so it matches the opaque light-mode appearance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix the lint error of commit
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
* fix(frontend): add missing rel="noopener noreferrer" to target="_blank" links
Prevent tabnabbing attacks and referrer leakage by ensuring all
external links with target="_blank" include both noopener and
noreferrer in the rel attribute.
Made-with: Cursor
* style: fix code formatting
* fix: use create_chat_model for summarization alias
* fix: remove unused radix Icon import from suggestion
---------
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
- Extract removeCitationsBlocks in utils, reuse in parseCitations and removeAllCitations
- Add hasCitationsBlock; isCitationsBlockIncomplete now uses it
- Add useParsedCitations hook (parseCitations + buildCitationMap) for message/artifact
- Add CitationAwareLink to unify link rendering (message-list-item + artifact-file-detail)
- Add getCleanContent helper; message-group uses it and useParsedCitations
- ArtifactFileDetail: single useParsedCitations, pass cleanContent/citationMap to Preview
- Stop exporting buildCitationMap and removeCitationsBlocks from citations index
- Remove duplicate MessageLink and inline link logic in artifact preview
Co-authored-by: Cursor <cursoragent@cursor.com>
- About: use aboutMarkdown from about-content.ts instead of raw-loader for
about.md (fixes Turbopack 'Cannot find module raw-loader')
- Web search: remove Tooltip from web_search and web_fetch result links
- Citations: remove HoverCard from CitationLink so no hover popup on badges
Co-authored-by: Cursor <cursoragent@cursor.com>
When citation data is not available, use the markdown link text
(children) as display text instead of just the domain. This ensures
that links like [OpenJudge](github.com/...) show 'OpenJudge' instead
of just 'github.com'.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Use citation.title for display text in CitationLink to ensure correct
titles show during streaming (instead of generic "Source" text)
- Render all external links as CitationLink badges for consistent styling
during streaming output
- Add removeAllCitations when copying message content to clipboard
- Simplify citations_format prompt for cleaner AI output
Co-authored-by: Cursor <cursoragent@cursor.com>