Skip to content

Commit 393362d

Browse files
jonathanlabclaude
andcommitted
feat: enhanced CLI panel with file mentions and loading states
## Summary - Add file path highlighting with @ mentions in CLI and RichTextEditor - Implement scrollable file hints with wrap-around navigation - Add loading state with animated dots during task creation - Persist CLI panel width with Zustand - Fix linter errors with proper TypeScript types ## Changes - Created FilePathHighlight extension for detecting and styling @file mentions - Added scrolling window for file hints (shows 10 at a time) - Implemented wrap-around navigation for file hints - Added spinner and "Spawning task..." indicator during task creation - Made CLI panel width persistent using Zustand store - Disabled spell checking in CLI editor - Made caret semi-transparent with color blend mode - Added proper TypeScript types for ProseMirror nodes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent c3c71de commit 393362d

File tree

5 files changed

+534
-20
lines changed

5 files changed

+534
-20
lines changed

src/renderer/components/FolderPicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export function FolderPicker({
180180
<Popover.Root open={open} onOpenChange={handleOpenChange}>
181181
<Popover.Trigger>
182182
<Button
183-
variant="surface"
183+
variant="outline"
184184
size={size}
185185
color="gray"
186186
style={{ width: "100%" }}

src/renderer/components/RichTextEditor.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { Box } from "@radix-ui/themes";
22
import type { MentionItem } from "@shared/types";
3+
import { Extension } from "@tiptap/core";
34
import { Link } from "@tiptap/extension-link";
45
import { Mention } from "@tiptap/extension-mention";
56
import { Placeholder } from "@tiptap/extension-placeholder";
67
import { Typography } from "@tiptap/extension-typography";
78
import { Underline } from "@tiptap/extension-underline";
9+
import type { Node } from "@tiptap/pm/model";
10+
import { Plugin, PluginKey } from "@tiptap/pm/state";
11+
import { Decoration, DecorationSet } from "@tiptap/pm/view";
812
import { EditorContent, useEditor } from "@tiptap/react";
913
import StarterKit from "@tiptap/starter-kit";
1014
import type { SuggestionOptions } from "@tiptap/suggestion";
@@ -19,6 +23,56 @@ import { markdownToTiptap, tiptapToMarkdown } from "../utils/tiptap-converter";
1923
import { MentionList, type MentionListRef } from "./FileMentionList";
2024
import { FormattingToolbar } from "./FormattingToolbar";
2125

26+
const FilePathHighlight = Extension.create({
27+
name: "filePathHighlight",
28+
29+
addProseMirrorPlugins() {
30+
return [
31+
new Plugin({
32+
key: new PluginKey("filePathHighlight"),
33+
state: {
34+
init(_, { doc }) {
35+
return findFilePathDecorations(doc);
36+
},
37+
apply(tr, oldState) {
38+
return tr.docChanged ? findFilePathDecorations(tr.doc) : oldState;
39+
},
40+
},
41+
props: {
42+
decorations(state) {
43+
return this.getState(state);
44+
},
45+
},
46+
}),
47+
];
48+
},
49+
});
50+
51+
function findFilePathDecorations(doc: Node) {
52+
const decorations: Decoration[] = [];
53+
const regex = /@[^\s]+/g;
54+
55+
doc.descendants((node: Node, pos: number) => {
56+
if (node.isText && node.text) {
57+
let match: RegExpExecArray | null = null;
58+
regex.lastIndex = 0;
59+
match = regex.exec(node.text);
60+
while (match !== null) {
61+
const from = pos + match.index;
62+
const to = from + match[0].length;
63+
decorations.push(
64+
Decoration.inline(from, to, {
65+
class: "rich-text-file-path",
66+
}),
67+
);
68+
match = regex.exec(node.text);
69+
}
70+
}
71+
});
72+
73+
return DecorationSet.create(doc, decorations);
74+
}
75+
2276
interface RichTextEditorProps {
2377
value: string;
2478
onChange: (value: string) => void;
@@ -201,6 +255,7 @@ export function RichTextEditor({
201255
let root: ReturnType<typeof createRoot> | null = null;
202256

203257
return {
258+
// biome-ignore lint/suspicious/noExplicitAny: TipTap suggestion API doesn't provide types
204259
onStart: (props: any) => {
205260
popup = document.createElement("div");
206261
popup.style.position = "absolute";
@@ -237,9 +292,11 @@ export function RichTextEditor({
237292
);
238293

239294
// Store ref for keyboard handling
295+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic property for component reference
240296
(component as any).ref = ref;
241297
},
242298

299+
// biome-ignore lint/suspicious/noExplicitAny: TipTap suggestion API doesn't provide types
243300
onUpdate: (props: any) => {
244301
if (!root) return;
245302

@@ -271,11 +328,14 @@ export function RichTextEditor({
271328
}
272329

273330
// Store ref for keyboard handling
331+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic property for component reference
274332
(component as any).ref = ref;
275333
},
276334

335+
// biome-ignore lint/suspicious/noExplicitAny: TipTap suggestion API doesn't provide types
277336
onKeyDown: (props: any) => {
278337
if (!component) return false;
338+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic property for component reference
279339
const ref = (component as any).ref;
280340
if (ref?.current?.onKeyDown) {
281341
return ref.current.onKeyDown(props);
@@ -293,6 +353,7 @@ export function RichTextEditor({
293353
},
294354
} as Partial<SuggestionOptions>,
295355
}),
356+
FilePathHighlight,
296357
],
297358
content: markdownToTiptap(value),
298359
onUpdate: ({ editor }) => {
@@ -475,6 +536,17 @@ export function RichTextEditor({
475536
>
476537
{showToolbar && editor && <FormattingToolbar editor={editor} />}
477538
<EditorContent editor={editor} />
539+
<style>
540+
{`
541+
.rich-text-file-path {
542+
background-color: var(--accent-a3);
543+
color: var(--accent-11);
544+
padding: 2px 4px;
545+
border-radius: 3px;
546+
font-weight: 500;
547+
}
548+
`}
549+
</style>
478550
</Box>
479551
);
480552
}

0 commit comments

Comments
 (0)