Skip to content

fix(runner): assistant follow-ups — event rate limit, Markdown rendering, Ask AI tooltip (DEV-2047) - #120

Merged
qunabu merged 2 commits into
masterfrom
fix/DEV-2047-event-rate-limit
Aug 7, 2026
Merged

fix(runner): assistant follow-ups — event rate limit, Markdown rendering, Ask AI tooltip (DEV-2047)#120
qunabu merged 2 commits into
masterfrom
fix/DEV-2047-event-rate-limit

Conversation

@qunabu

@qunabu qunabu commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Three follow-ups to #119.

1. /api/chat/event gets its own rate-limit bucket

A regression I introduced in #119 while fixing the unbounded-dimensions finding, and merged before the review comment landed. The event route reused checkChatRateLimit, so Apply/Undo incremented the same per-IP counters as paid questions:

  • applying an edit silently spent the user's question quota;
  • users near the 8/min cap lost chat_edit_applied counts — skewing the acceptance metric the panel exists to show;
  • anyone could exhaust an IP's chat budget by spamming the free route.
Route Per minute Per day
/api/chat 8 120
/api/chat/event 60 600

Verified: 12 consecutive events all return 204 (the shared bucket refused after 8), and a question afterwards still succeeds.

2. Answers render as Markdown

The panel shipped with a placeholder renderer that understood fenced code and paragraphs and nothing else, so every **bold**, inline code and bullet list appeared as literal asterisks and backticks. The content was right; the presentation made it look broken.

There is now a real renderer for the subset answers actually use — headings, ordered/unordered lists, bold, italic, inline code, links, fenced code. It builds React elements and never touches innerHTML, so model output cannot inject markup, and links are restricted to http(s). A Markdown library would have meant a new dependency in the authoring bundle and, in most cases, an HTML sink to sanitise. Unsupported syntax falls through as text.

3. The Ask AI button explains itself

"Ask AI" is a label, not an invitation — nobody clicks a chat button to find out it can edit their code. A hover/focus tooltip now names the three things worth knowing: scoped to the open example, changes arrive as an edit you can apply, answers are grounded in the docs with links. It also states up front that nothing is applied without you, which is the question people ask before letting an assistant near their code, and it hides once the panel is open.


Note

Medium Risk
Separate rate-limit buckets fix a real quota/abuse regression on a public API; the Markdown renderer touches untrusted model output but only allows http(s) links and avoids innerHTML.

Overview
Rate limiting: POST /api/chat/event no longer shares per-IP counters with paid /api/chat. checkChatRateLimit now takes a bucket (chat vs event) with separate limits (8/120 vs 60/600 per min/day), so Apply/Undo analytics do not consume question quota and the free route cannot exhaust an IP’s LLM budget.

Authoring UX: The toolbar Ask AI control is extracted into AskAiButton with a hover/focus tooltip that explains example-scoped Q&A, doc grounding, and apply/undo. Chat replies swap the inline Markdownish helper for a shared Markdown component that renders headings, lists, bold/italic, fenced code, and http(s) links via React (no innerHTML).

Docs in example-chat.md document the second bucket.

Reviewed by Cursor Bugbot for commit 362d98b. Bugbot is set up for automated code reviews on this repo. Configure here.

qunabu added 2 commits August 6, 2026 15:24
Reusing checkChatRateLimit for the event route made Apply/Undo increment the
same per-IP counters as paid questions: applying edits silently spent a user's
question quota, active users near the 8/min cap lost chat_edit_applied counts,
and anyone could exhaust an IP's chat budget through the free route.

Two buckets now, because the routes cost wildly different amounts — an answer
is an LLM call, an event is one counter row. Chat stays 8/min and 120/day;
events get 60/min and 600/day under their own keys.

A regression I introduced while fixing the unbounded-dimensions finding, and it
landed on the commit as it was being merged. Verified: 12 consecutive events
all return 204 and a question afterwards still succeeds.
… (DEV-2047)

Answers were rendered by a placeholder that understood fenced code blocks and
paragraphs and nothing else, so every **bold**, `inline code` and bullet list
came out as literal asterisks and backticks — the content was right and the
presentation made it look broken.

There is now a real renderer for the subset answers actually use: headings,
ordered and unordered lists, bold, italic, inline code, links, fenced code.
It builds React elements and never touches innerHTML, so model output cannot
inject markup no matter what it contains, and links are restricted to http(s)
— a Markdown library would have added a dependency and, in most cases, an HTML
sink to sanitise. Anything unsupported falls through as text, which is the
right failure: an unrendered character is a blemish, a broken renderer is a
broken answer.

The toolbar button also gets a tooltip. "Ask AI" is a label, not an invitation
— nobody clicks a chat button to discover it can edit their code. The hint
names the three things worth knowing: it is scoped to the open example, a
change arrives as an edit you can apply, and answers are grounded in the docs.
It also says up front that nothing is applied without you, because that is the
question a user asks before letting an assistant near their code. It hides once
the panel is open, where it would only repeat what is on screen.
@qunabu qunabu changed the title fix(runner): give /api/chat/event its own rate-limit bucket (DEV-2047) fix(runner): assistant follow-ups — event rate limit, Markdown rendering, Ask AI tooltip (DEV-2047) Aug 7, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 362d98b. Configure here.

}
}

const INLINE = /(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*\n]+\*)|(_[^_\n]+_)|(\[[^\]]+\]\([^)\s]+\))/g;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Underscore italics break snake_case

Medium Severity

The _italic_ alternative in INLINE matches any underscore pair, including spaces and mid-identifier characters, so phrases like first_name and last_name render as italicized spans instead of literal text. Chat answers and user turns both go through Markdown, so ordinary snake_case column names get mangled unless wrapped in backticks.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 362d98b. Configure here.

@qunabu
qunabu merged commit 150b0cb into master Aug 7, 2026
3 checks passed
@demtario
demtario deleted the fix/DEV-2047-event-rate-limit branch August 10, 2026 06:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant