<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ankush Patil</title>
    <description>The latest articles on DEV Community by Ankush Patil (@ankush_patil_ccb789c51a94).</description>
    <link>https://dev.to/ankush_patil_ccb789c51a94</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3089720%2Fb18d1366-6a05-4856-a2d0-386d605d3d97.jpg</url>
      <title>DEV Community: Ankush Patil</title>
      <link>https://dev.to/ankush_patil_ccb789c51a94</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ankush_patil_ccb789c51a94"/>
    <language>en</language>
    <item>
      <title>Building Cursor-style inline AI editing in React with Anky UI</title>
      <dc:creator>Ankush Patil</dc:creator>
      <pubDate>Thu, 29 Oct 2026 02:27:59 +0000</pubDate>
      <link>https://dev.to/ankush_patil_ccb789c51a94/building-cursor-style-inline-ai-editing-in-react-with-anky-ui-28mn</link>
      <guid>https://dev.to/ankush_patil_ccb789c51a94/building-cursor-style-inline-ai-editing-in-react-with-anky-ui-28mn</guid>
      <description>&lt;p&gt;A user wants a shorter paragraph. Moving that paragraph into a chat window adds a second workflow: copy the text, switch context, explain the task, copy the answer, paste it back, then remove the conversational preamble.&lt;/p&gt;

&lt;p&gt;An inline editor can keep the task in one place: select, rewrite, compare, accept. Anky UI supplies the interaction primitives for that flow. This article walks through how to connect them without giving the model direct ownership of your document.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/ErAnkushPatil/anky-ui" rel="noopener noreferrer"&gt;https://github.com/ErAnkushPatil/anky-ui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Video walkthrough: &lt;a href="https://x.com/ErAnkushP/status/2115629939106230425" rel="noopener noreferrer"&gt;https://x.com/ErAnkushP/status/2115629939106230425&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the suggestion from the saved text
&lt;/h2&gt;

&lt;p&gt;Treat the model’s response as a proposal. Keep the original text, the in-progress rewrite, and the committed document distinct. That lets a user reject a change and makes network failure recoverable.&lt;/p&gt;

&lt;p&gt;Start with the diff primitive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;InlineDiff&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@erankushpatil/anky-ui&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ReviewEdit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Our team shipped the new onboarding flow this quarter.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setProposal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setProposal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;We shipped a new onboarding flow.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Preview an example edit
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;proposal&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InlineDiff&lt;/span&gt;
          &lt;span class="na"&gt;originalText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;modifiedText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;proposal&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;onAccept&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;setProposal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;onReject&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setProposal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example deliberately uses a fixed proposal so the review and commit behavior can be tried without a backend. The application updates its document only in &lt;code&gt;onAccept&lt;/code&gt;. Rendering a diff alone does not replace selected DOM text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anchor actions to the selection
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SelectionPalette&lt;/code&gt; uses floating-ui to position actions next to highlighted text. Its callback gives your application the prompt and selected text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SelectionPalette&lt;/span&gt;
  &lt;span class="na"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Shorten&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Make this shorter. Return only the revised text.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fix grammar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fix grammar. Return only the revised text.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onSelectPrompt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;requestRewrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;editorRef&lt;/code&gt; and &lt;code&gt;requestRewrite&lt;/code&gt; belong to your application. Scope the palette to the editor so selecting navigation text elsewhere does not open an editing action. Decide what should happen if the document changes while the request is running; a stale range should not overwrite newer user edits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stream into state with a rollback boundary
&lt;/h2&gt;

&lt;p&gt;The state hook captures a snapshot before a stream begins. On failure or abort, it can return to that snapshot.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;useOptimisticAIState&lt;/code&gt; to track the streamed proposal. Its &lt;code&gt;mutateWithStream(source, transformFn)&lt;/code&gt; API accepts a response or stream factory and folds chunks into state. Pass the supplied abort signal to your fetch request. Initialize each proposal with the selected original text and an empty modified value, then append incoming chunks to that proposal. Connect &lt;code&gt;currentState&lt;/code&gt; and &lt;code&gt;isStreaming&lt;/code&gt; to &lt;code&gt;InlineDiff&lt;/code&gt;. The application still owns applying accepted text to the document.&lt;/p&gt;

&lt;p&gt;Your server route calls the model and returns a UTF-8 text stream or text SSE. Keep provider credentials on the server, and handle unsuccessful HTTP responses before treating their bodies as rewrite text. Provider helpers also decode supported OpenAI, Anthropic, and Vercel AI SDK formats into text tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep keyboard controls predictable
&lt;/h2&gt;

&lt;p&gt;The diff supports Cmd/Ctrl+Enter to accept and Escape to reject. Accept is disabled during streaming. GhostInput uses Tab or right arrow at the end of the textarea to accept a continuation, while Escape clears it.&lt;/p&gt;

&lt;p&gt;Test these controls alongside the rest of your editor. A shortcut that works in isolation may conflict with a parent dialog, an input method, or an editor command. Rich-text adapters are provided, but the repository explicitly recommends checking them against your editor version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the primitives
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @erankushpatil/anky-ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For source ownership:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx anky-ui add selection-palette
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository also includes a mock-stream playground:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ErAnkushPatil/anky-ui.git
&lt;span class="nb"&gt;cd &lt;/span&gt;anky-ui
pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;pnpm dev:demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anky UI supports React 18/19 and Next.js App Router Client Components. Tailwind presets are optional. Start with the included demo, then test cancellation, rapid successive rewrites, selection changes, and accepting an edit after the document has changed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I’m the author of Anky UI. This article was prepared with ChatGPT using the repository documentation. The code is an illustrative integration example.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
    </item>
  </channel>
</rss>
