Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ import * as DesktopClientSettings from "./DesktopClientSettings.ts";

const clientSettings: ClientSettings = {
autoOpenPlanSidebar: false,
collapsedAgentsSlashCommands: false,
collapsedCustomSlashCommands: false,
confirmThreadArchive: true,
confirmThreadDelete: false,
customSlashCommands: [],
dismissedProviderUpdateNotificationKeys: [],
diffIgnoreWhitespace: true,
favorites: [],
hiddenCustomSlashCommands: [],
hiddenGlobalSlashCommands: [],
hiddenProviderSlashCommands: {},
collapsedProviderSlashCommandProviders: [],
providerModelPreferences: {},
sidebarProjectGroupingMode: "repository_path",
sidebarProjectGroupingOverrides: {
Expand Down
17 changes: 17 additions & 0 deletions apps/web/src/components/AppLogoIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { cn } from "~/lib/utils";

export function AppLogoIcon(props: { className?: string }) {
return (
<svg
aria-hidden="true"
className={cn("shrink-0 text-current", props.className)}
viewBox="15.5309 37 94.3941 56.96"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z"
fill="currentColor"
/>
</svg>
);
}
19 changes: 17 additions & 2 deletions apps/web/src/components/ChatMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
import remarkBreaks from "remark-breaks";
import remarkGfm from "remark-gfm";
import { renderSkillInlineMarkdownChildren } from "./chat/SkillInlineText";
import { renderSlashCommandInlineMarkdownChildren } from "./chat/SlashCommandInlineText";
import type { ComposerSlashCommandLike } from "~/lib/composerSlashCommands";
import { CHAT_FILE_TAG_CHIP_CLASS_NAME, FileTagChipContent } from "./chat/FileTagChip";
import { PierreEntryIcon } from "./chat/PierreEntryIcon";
import { hasSpecificPierreIconForFileName, syntheticFileNameForLanguageId } from "../pierre-icons";
Expand Down Expand Up @@ -112,12 +114,14 @@ interface ChatMarkdownProps {
onTaskListChange?: ((input: { markerOffset: number; checked: boolean }) => void) | undefined;
isStreaming?: boolean;
skills?: ReadonlyArray<Pick<ServerProviderSkill, "name" | "displayName">>;
slashCommands?: ReadonlyArray<ComposerSlashCommandLike>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/ChatMarkdown.tsx:117

The markdownComponents memo omits slashCommands from its dependency array, so when the slashCommands prop changes while ChatMarkdown is mounted — e.g. after creating or editing a custom command — the memoized p/li renderers keep using the stale list and command highlighting never updates until an unrelated dependency changes. Add slashCommands to the memo's dependency array.

Also found in 1 other location(s)

apps/web/src/components/ComposerPromptEditor.tsx:681

slashCommandSignature omits displayName, provider, and sourceKind, even though all three are copied into ComposerSlashCommandNode metadata. If one of those fields changes while name, description, and input.hint remain unchanged (for example, editing only the capitalization of a custom command title so its slug stays the same), slashCommandsChanged remains false and the controlled-update effect does not rewrite the editor state. Existing slash-command tokens therefore continue displaying stale labels/source metadata until some unrelated value changes.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatMarkdown.tsx around line 117:

The `markdownComponents` memo omits `slashCommands` from its dependency array, so when the `slashCommands` prop changes while `ChatMarkdown` is mounted — e.g. after creating or editing a custom command — the memoized `p`/`li` renderers keep using the stale list and command highlighting never updates until an unrelated dependency changes. Add `slashCommands` to the memo's dependency array.

Also found in 1 other location(s):
- apps/web/src/components/ComposerPromptEditor.tsx:681 -- `slashCommandSignature` omits `displayName`, `provider`, and `sourceKind`, even though all three are copied into `ComposerSlashCommandNode` metadata. If one of those fields changes while `name`, `description`, and `input.hint` remain unchanged (for example, editing only the capitalization of a custom command title so its slug stays the same), `slashCommandsChanged` remains false and the controlled-update effect does not rewrite the editor state. Existing slash-command tokens therefore continue displaying stale labels/source metadata until some unrelated value changes.

className?: string;
/** Treat single newlines as hard breaks — chat-style user input. */
lineBreaks?: boolean;
}

const EMPTY_MARKDOWN_SKILLS: ReadonlyArray<Pick<ServerProviderSkill, "name" | "displayName">> = [];
const EMPTY_MARKDOWN_SLASH_COMMANDS: ReadonlyArray<ComposerSlashCommandLike> = [];

const CODE_FENCE_LANGUAGE_REGEX = /(?:^|\s)language-([^\s]+)/;
const MAX_HIGHLIGHT_CACHE_ENTRIES = 500;
Expand Down Expand Up @@ -1254,6 +1258,7 @@ function ChatMarkdown({
onTaskListChange,
isStreaming = false,
skills = EMPTY_MARKDOWN_SKILLS,
slashCommands = EMPTY_MARKDOWN_SLASH_COMMANDS,
className,
lineBreaks = false,
}: ChatMarkdownProps) {
Expand Down Expand Up @@ -1348,15 +1353,25 @@ function ChatMarkdown({
const markdownComponents = useMemo<Components>(
() => ({
p({ node: _node, children, ...props }) {
return <p {...props}>{renderSkillInlineMarkdownChildren(children, skills)}</p>;
return (
<p {...props}>
{renderSlashCommandInlineMarkdownChildren(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High components/ChatMarkdown.tsx:1358

Slash-command tokens in paragraph and list text are never converted into chips. renderSlashCommandInlineMarkdownChildren is called on the output of renderSkillInlineMarkdownChildren, which already replaced every string child with a SkillInlineText element that has no children prop. Since renderSlashCommandInlineMarkdownChildren only transforms string nodes, it finds no strings left and skips everything, so slash-command rendering in p and li is a no-op. Apply both transforms to the original string children (e.g. via a combined renderer) instead of chaining them.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatMarkdown.tsx around line 1358:

Slash-command tokens in paragraph and list text are never converted into chips. `renderSlashCommandInlineMarkdownChildren` is called on the output of `renderSkillInlineMarkdownChildren`, which already replaced every string child with a `SkillInlineText` element that has no `children` prop. Since `renderSlashCommandInlineMarkdownChildren` only transforms string nodes, it finds no strings left and skips everything, so slash-command rendering in `p` and `li` is a no-op. Apply both transforms to the original string children (e.g. via a combined renderer) instead of chaining them.

renderSkillInlineMarkdownChildren(children, skills),
slashCommands,
)}
</p>
);
},
li({ node, children, ...props }) {
const listItemStart = node?.position?.start.offset;
const markerOffset =
typeof listItemStart === "number" ? findTaskListMarkerOffset(text, listItemStart) : null;
return (
<li {...props} data-task-marker-offset={markerOffset ?? undefined}>
{renderSkillInlineMarkdownChildren(children, skills)}
{renderSlashCommandInlineMarkdownChildren(
renderSkillInlineMarkdownChildren(children, skills),
slashCommands,
)}
</li>
);
},
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import {
collapseExpandedComposerCursor,
parseStandaloneComposerSlashCommand,
} from "../composer-logic";
import { collectComposerSlashCommands } from "../lib/composerSlashCommands";
import {
derivePendingApprovals,
derivePendingUserInputs,
Expand Down Expand Up @@ -2290,6 +2291,14 @@ function ChatViewContent(props: ChatViewProps) {
const defaultInstanceId = defaultInstanceIdForDriver(selectedProvider);
return providerStatuses.find((status) => status.instanceId === defaultInstanceId) ?? null;
}, [activeProviderInstanceId, providerStatuses, selectedProvider]);
const visibleSlashCommands = useMemo(
() =>
collectComposerSlashCommands(providerStatuses, {
hiddenSlashCommandsByProvider: settings.hiddenProviderSlashCommands,
customSlashCommands: settings.customSlashCommands,
}),
[providerStatuses, settings.customSlashCommands, settings.hiddenProviderSlashCommands],
);
const activeProjectCwd = activeProject?.workspaceRoot ?? null;
const activeThreadWorktreePath = activeThread?.worktreePath ?? null;
const activeWorkspaceRoot = activeThreadWorktreePath ?? activeProjectCwd ?? undefined;
Expand Down Expand Up @@ -5279,6 +5288,7 @@ function ChatViewContent(props: ChatViewProps) {
onAnchorReady={onTimelineAnchorReady}
onAnchorSizeChanged={onTimelineAnchorSizeChanged}
contentInsetEndAdjustment={composerOverlayHeight}
slashCommands={visibleSlashCommands}
onIsAtEndChange={onIsAtEndChange}
onManualNavigation={cancelTimelineLiveFollowForUserNavigation}
hideEmptyPlaceholder={isDraftHeroState}
Expand Down
Loading
Loading