From 1a4728c255af0956f4b80f29e43df73e74449a4d Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Mon, 11 May 2026 12:32:24 +0000 Subject: [PATCH] feat: style skill slash commands as pill nodes in conversation textarea Adds a SkillMention Tiptap inline node (atom) that replaces the plain text insertion when a skill is selected from the slash-command dropdown. The node renders as a rounded pill with primary color text and a 15% primary alpha background, matching Slack-style slash command treatment. Registered in both ConversationTextarea and ConversationNew editors. Co-Authored-By: Claude Sonnet 4.6 --- .../conversation-textarea.client.tsx | 2 + .../conversation/conversation.client.tsx | 2 + .../conversation/slash-command-extension.ts | 44 ++++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/components/conversation/conversation-textarea.client.tsx b/apps/webapp/app/components/conversation/conversation-textarea.client.tsx index c8e2a25d0..4d4278078 100644 --- a/apps/webapp/app/components/conversation/conversation-textarea.client.tsx +++ b/apps/webapp/app/components/conversation/conversation-textarea.client.tsx @@ -19,6 +19,7 @@ import { } from "../ui/select"; import { createSkillSlashCommand, + SkillMention, SkillSlashPluginKey, } from "./slash-command-extension"; @@ -97,6 +98,7 @@ export function ConversationTextarea({ includeChildren: true, }), History, + SkillMention, createSkillSlashCommand(skillsRef), ], immediatelyRender: false, diff --git a/apps/webapp/app/components/conversation/conversation.client.tsx b/apps/webapp/app/components/conversation/conversation.client.tsx index cc3647f71..76a0e602f 100644 --- a/apps/webapp/app/components/conversation/conversation.client.tsx +++ b/apps/webapp/app/components/conversation/conversation.client.tsx @@ -27,6 +27,7 @@ import type { LLMModel } from "./conversation-textarea.client"; import Avatar from "boring-avatars"; import { createSkillSlashCommand, + SkillMention, SkillSlashPluginKey, } from "./slash-command-extension"; @@ -124,6 +125,7 @@ export const ConversationNew = ({ Text, HardBreak.configure({ keepMarks: true }), History, + SkillMention, createSkillSlashCommand(skillsRef), ], immediatelyRender: false, diff --git a/apps/webapp/app/components/conversation/slash-command-extension.ts b/apps/webapp/app/components/conversation/slash-command-extension.ts index f392e935b..d2735a4fa 100644 --- a/apps/webapp/app/components/conversation/slash-command-extension.ts +++ b/apps/webapp/app/components/conversation/slash-command-extension.ts @@ -1,4 +1,4 @@ -import { Extension } from "@tiptap/core"; +import { Extension, Node, mergeAttributes } from "@tiptap/core"; import { PluginKey } from "@tiptap/pm/state"; import { ReactRenderer } from "@tiptap/react"; import Suggestion from "@tiptap/suggestion"; @@ -92,6 +92,43 @@ const SkillCommandList = forwardRef( ); SkillCommandList.displayName = "SkillCommandList"; +export const SkillMention = Node.create({ + name: "skillMention", + group: "inline", + inline: true, + selectable: false, + atom: true, + + addAttributes() { + return { + slug: { + default: null, + parseHTML: (el) => el.getAttribute("data-slug"), + renderHTML: (attrs) => ({ "data-slug": attrs.slug }), + }, + }; + }, + + parseHTML() { + return [{ tag: 'span[data-type="skill-mention"]' }]; + }, + + renderHTML({ node, HTMLAttributes }) { + return [ + "span", + mergeAttributes({ "data-type": "skill-mention" }, HTMLAttributes, { + class: + "skill-mention rounded px-1.5 py-0.5 text-primary bg-primary/15 font-medium text-sm", + }), + `/${node.attrs.slug}`, + ]; + }, + + renderText({ node }) { + return `/${node.attrs.slug}`; + }, +}); + export const SkillSlashPluginKey = new PluginKey("skillSlashCommand"); export function createSkillSlashCommand( @@ -111,7 +148,10 @@ export function createSkillSlashCommand( .chain() .focus() .deleteRange(range) - .insertContent(`/${props.slug} `) + .insertContent([ + { type: "skillMention", attrs: { slug: props.slug } }, + { type: "text", text: " " }, + ]) .run(); }, items: ({ query }: { query: string }) => {